简体   繁体   中英

Use of generic types in overloaded methods

I have a generic method:

public bool DoSomething<T>(T item) where T: IBase
{
    return DoSomethingSpecific(item);
}

public bool DoSomethingSpecific(IBase item)
{
    return true;
}

public bool DoSomethingSpecific(IAdvanced item)
{
    return false;
}

Note that IAdvanced interface derives/inherits from IBase interface.

I have found that if I call DoSomething where the item is of type IAdvanced, it still always returns false. I don't understand this. I know that since IAdvanced is a type IBase (as it is a child of this interface), it may cause confusion between the 2 overloaded types of the DoSomethingSpecific method. However, as I understand with my limited C# knowledge, the IAdvanced method should be chosen here. This is an example of how I made this conclusion:

public class Advanced: IAdvanced
{

   public void CallMethod()
   {
      DoSomething(this);
   }
}

This results in a true value.

However, if I do:

public class Advanced: IAdvanced
{

    public void CallMethod()
    {
       DoSomethingSpecific(this);
    }
}

It returns false, which is what I would expect.

I have to say that I have never used generics before. I have attempted though, but always get stuck on a case like this, and then completely fail to see the point of using generics (besides data structures such as trees and linked lists).

This time I decided to come here for some advice. Is there a clear problem with what I am trying to do? Does it perhaps not make sense to try and do what I am busy doing here?

I can't reproduce this, which means that something else is probably going on. I suspect you actually meant to say that it always returns true . That's what I see here:

using System;

public interface IBase {}
public interface IAdvanced : IBase {}

public class Base : IBase {}
public class Advanced : IAdvanced {}

class Test
{
    public static bool DoSomething<T>(T item) where T: IBase
    {
        return DoSomethingSpecific(item);
    }

    public static bool DoSomethingSpecific(IBase item)
    {
        return true;
    }

    public static bool DoSomethingSpecific(IAdvanced item)
    {
        return false;
    }

    static void Main()
    {
        Console.WriteLine(DoSomething(new Base()));     // True
        Console.WriteLine(DoSomething(new Advanced())); // True
    }    
}

Now you also write:

I know that since IAdvanced is a type IBase (as it is a child of this interface), it may cause confusion between the 2 overloaded types of the DoSomethingSpecific method. However, as I understand with my limited C# knowledge, the IAdvanced method should be chosen here.

Surely then you should expect false to be returned - whereas I expect true to be returned.

You see, the overload resolution within DoSomething<T> is determined when that method is compiled. That choice is made once - not once for every different T . So if you look in the compiled IL for DoSomething<T> , you'll only see a call to DoSomethingSpecific(IBase) , never DoSomethingSpecific(IAdvanced) . There's no polymorphism here.

As for how you can "fix" this - you'd need to explain in more detail what you really want to achieve. In particular, what would you want to happen if you had this code:

IBase x = new AdvancedImplementation();
DoSomething(x);

Here T would be IBase , but the value would refer to an implementation of IAdvanced . If you want DoSomethingSpecific(IAdvanced) to be executed in this case, and if you're using C# 4, then you can use dynamic typing:

public static bool DoSomething(IBase item)
{
    dynamic dynamicItem = item;
    // Dispatch dynamically based on execution-time type
    return DoSomethingSpecific(dynamicItem);
}

Note how the method doesn't need to be generic any more - it would give no benefit.

If, on the other hand, you want to decide which to call solely based on T , you'd need to use something like ivowiblo's solution.

Personally I would try to avoid both of these solutions - I usually find that this sort of thing is a symptom of a design which can be improved in other ways.

I would drop the compile-time check and make it runtime:

public bool DoSomething(IBase item)
{
    var itemAdvanced = item as IAdvanced;
    if (itemAdvanced != null)
        return DoSomethingSpecific(itemAdvanced);
    else
        return DoSomethingSpecific(item);
}

The reason is that if your caller only statically knows of the object as an IBase , but at runtime it's an IAdvanced , wouldn't it be preferred to treat it as an IAdvanced ? This is probably also the fastest way to do what you want. I'd only go for the dynamic approach if you see a need to, eg because there could be a great number of different methods.

As far as it knows, it's an IBase. The compiler needs to deside which method are you calling and that's why it's choosing that one always.

A dirty trick will be to do this:

public static bool DoSomething<T>(T item) where T: IBase
{
    var isAdvanced = typeof(IAdvanced).IsAssignableFrom(typeof(T));
    return isAdvanced ? DoSomethingSpecific((IAdvanced)item) : DoSomethingSpecific(item);
}

Another way is to use Double-Dispatch/Visitor pattern:

public interface IDoSomethingVisitor {
    bool DoSomethingSpecific(IBase base);
    bool DoSomethingSpecific(IAdvanced adv);
}

The DoSomething Method will be in your IBase interface:

public interface IBase{
    void DoSomething(IDoSomethingVisitor visitor);
}

And in your implementations:

public class Base : IBase
{
   public bool DoSomething(IDoSomethingVisitor visitor)
   {
      visitor.DoSomething(this);
   }
}

public class Advanced : IAdvanced
{
   public bool DoSomething(IDoSomethingVisitor visitor)
   {
      visitor.DoSomething(this);
   }
}

In this case, the problem is solved using pure inheritance. The actual instance is the one that resolves which method to call. No ifs.

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