开发者

why is this generic not resolved at compile time?

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; 开发者_StackOverflowset; }
    }

    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();
        }
    }
}


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.


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"
}


The item will ALWAYS be validated as type Parent.

The idea behind generics is that you do NOT have type specific code.. hence the "generic" part of it.

You are limiting to a branch of classes, in this case Parent and everything that descends from it. This means the code should execute as if the object being passed in was of type Parent.

As dtb said, the Visitor pattern could apply here.

Another idea would be to simply have an interface which defined the Validate() method that each class had to support. I think this would be a better route to take.


Assuming you can use C# 4.0, you can defeat the static overload resolution that the C# compiler will do by using the "dynamic" keyword. Simply change your validate function to:

    void Validate<T>(Parent item) where T : Parent
    {
        dynamic dyn = item;
        if (item is T)
            Validate(dyn);
    }

and the output will be:

C:\tmp>temp.exe
A
B
C

DONE

I just learned this myself from @juharr's link to Eric Lippert's blog. Read more about the dynamic type on msdn.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜