简体   繁体   中英

C# Call shadow method with generic cast

I'm trying to write a method which casts an object to a generic type to execute a specific shadow method. This is my test code:

class Program
{
    static void Main(string[] args)
    {
        hello2 h2 = new hello2();
        test(h2);
        Console.ReadLine();
    }

    static void test(hello h)
    {
        h.write2<hello2>();
    }
}

class hello
{
    public virtual void write()
    {
        Console.WriteLine("hello");
    }

    public void write2<T>() where T : hello
    {
        T h2 = (T)this;
        hello2 h21 = (hello2)this;
        h2.write();
        h21.write();
    }
}

class hello2 : hello
{
    public new void write()
    {
        Console.WriteLine("hello2");
    }
}

My Console Output is:

hello

hello2

I debugged it, checked everything and couldn't find a mistake. The Output should be hello2 in both cases. Am I missing something obvious here? Or is this just not working?

The thing you are missing is the method that is called is chosen at the compile time of hello not at the use of write2 . So at compile time all the compiler knows is that T is of type hello or some other derived class so it chooses the shadowed method as that is the only method it knows about at compile time.

Think of it this way, replace every T with whatever is on the right side of the : . This is what the compiler sees and uses that information to make its choices.

//What the complier sees when you compile even if you pass in hello2 as T.
public void write2<T>() where T : hello
{
    hello h2 = (hello)this;
    hello2 h21 = (hello2)this;
    h2.write();
    h21.write();
}

When the method is declared as virtual the resolution of the method that will be called is deferred until runtime and depends on the type of the object at runtime. From the C# spec :

In a virtual method invocation, the run-time type of the instance for which that invocation takes place determines the actual method implementation to invoke.

From the C# spec , the resolution of a virtual method is as follows:

For every virtual method declared in or inherited by a class, there exists a most derived implementation of the method with respect to that class. The most derived implementation of a virtual method M with respect to a class R is determined as follows:

  • If R contains the introducing virtual declaration of M, then this is the most derived implementation of M.
  • Otherwise, if R contains an override of M , then this is the most derived implementation of M.
  • Otherwise, the most derived implementation of M with respect to R is the same as the most derived implementation of M with respect to the direct base class of R.

So when you write:

T h2 = (T)this;
h2.write();

The compiler knows h2 is of type hello or derived, and that write is a virtual method whose resolution will be deferred until runtime.

At runtime, the resolution will chose the method in class hello even though the class is of type hello2 . This is because the write method in class hello2 does not contains the override keyword and won't be considered when resolving the virtual call. (So the only way to call write on hello2 is by making sure the compiler knows the instance is of type hello2 )

This is also why if you declare write on hello2 with override instead of new the output of the same program would be hello2 in both cases.

Edit

It seems you want to write a method that is able to call write in hello or hello2 even though the method has been declared with the new operator in hello2 .

You could write a extension method that uses a dynamic variable. For example create the following extension method:

public static class HelloExtensions
{
    public static void writeDynamic<T>(this T h) where T: hello
    {
        dynamic hdynamic = h;
        hdynamic.write();
    }
}

Then you could write the following test program that uses that extension method with your original classes:

class Program
{
    static void Main(string[] args)
    {
        testDynamic(new hello());
        testDynamic(new hello2());
        Console.ReadLine();
    }

    static void testDynamic(hello h)
    {
        h.writeDynamic();
    }
}

The output will be:

hello
hello2

The write method that you are using in your method

public void write2<T>() where T : hello {
    T h2 = (T)this;
    hello2 h21 = (hello2)this;
    h2.write();  // <--- this one
    h21.write();
}

is the write method defined on the hello type (check it using intellisense). Therefore when you are actually calling the method with a specific T type of type hello2 it is still the write method if type hello . And since you are defining the write method in type hello2 using the new operator, this method is not called.

You can only use methods of type hello2 if you explicitly cast it to type hello2 in your method , and not to not-yet-known-type T .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM