Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why is this generic not resolved at compile time?

Tags:

c#

generics

I have the following code. I expect it to print:

A
B
C
DONE

instead it prints

P
P
P
DONE

why?

UPDATE
I'm not asking for a work around solution. I want to know why this is happening. I thought generics were resolved at compile time. From what I can tell it should be able to resolve these to the proper methods at compile time, but apparently it is not and I do not understand why. I am looking for an explanation of why, not a work around solution.

here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication50
{
    class Parent
    {
        public string FieldName { get; set; }
        public string Id { get; set; }
    }

    class ChildA : Parent
    {
        public string FieldValue { get; set; }
    }

    class ChildB : Parent
    {
        public DateTime? Start { get; set; }
        public DateTime? End { get; set; }
    }

    class ChildC : Parent
    {
        public ICollection<string> Values { get; set; }
    }
    class Program
    {
        void Validate<T>(Parent item) where T : Parent
        {
            if (item is T)
                Validate(item as T);
        }
        void Validate(ChildA filter)
        {
            Console.WriteLine("A");
        }

        void Validate(ChildB filter)
        {
            Console.WriteLine("B");
        }

        void Validate(ChildC filter)
        {
            Console.WriteLine("C");
        }

        void Validate(Parent filter)
        {
            Console.WriteLine("P");
            // do nothing placeholder so the code will compile
        }

        ArgumentException Fail(Parent filter, string message)
        {
            return new ArgumentException(message, filter.FieldName);
        }

        void Run()
        {
            var list = new List<Parent> {
                new ChildA(), new ChildB(), new ChildC() };
            Validate<ChildA>(list[0]);
            Validate<ChildB>(list[1]);
            Validate<ChildC>(list[2]);
        }
        public static void Main()
        {
            new Program().Run();
            Console.WriteLine();
            Console.WriteLine("DONE");
            Console.ReadLine();
        }
    }
}
like image 413
Charles Lambert Avatar asked Nov 29 '22 04:11

Charles Lambert


2 Answers

Generics are a run-time concept. This is their primary difference from C++ templates, which are a compile-time concept.

Within the method Validate<T>, T is always unknown at compile-time, even when explicitly specified by the caller. The only thing Validate<T> knows about T is that it descends from Parent.

More specifically, generics cannot be used to generate code. What you're trying would work under C++ because when C++ sees a call to Validate<ClassA>, it actually recompiles Validate<T>, so templates become a kind of code generation. Under C#, Validate<T> is only compiled once, so generics cannot be used as a kind of code generation.

Under C++, the call to Validate<ClassA> will instantiate the template at compile-time.

Under C#, the call to Validate<ClassA> will instatiate the generic method at run-time.

like image 170
Stephen Cleary Avatar answered Dec 13 '22 03:12

Stephen Cleary


Overload resolution is performed at compile-time, not at runtime.

The usual solution is to use simple virtual dispatch here:

class Parent
{
    public virtual  void Validate() { Console.WriteLine("P"); }
}

class ChildA : Parent
{
    public override void Validate() { Console.WriteLine("A"); }
}

class ChildB : Parent
{
    public override void Validate() { Console.WriteLine("B"); }
}

void Run()
{
    var list = new List<Parent> { new ChildA(), new ChildB() };
    list[0].Validate(); // prints "A"
    list[1].Validate(); // prints "B"
}
like image 26
dtb Avatar answered Dec 13 '22 03:12

dtb