问题 通过扩展方法进行多态性?


我有一个类库,其中包含一些基类和从中派生的其他基类。在这个类库中,我正在利用多态来做我想做的事情。现在在一个消费应用程序中,我想根据子类的运行时类型更改某些代码的行为。假设如下:

public class Base { }
public class Child1 : Base { }
public class Child2 : Base { }

现在在消费应用程序中,我想做一些如下操作(请注意,以下所有类都在使用应用程序中,并且不能在类库中引用):

public interface IMyInterface1 { }
public interface IMyInterface2 { }
public static class Extensions
{
    public static void DoSomething(this Base myObj, Object dependency)
    {

    }

    public static void DoSomething(this Child1 myObj, Object dependency)
    {
        IMyInterface1 myInterface = dependency as IMyInterface1;
        if (myInterface != null)
        {
            //Do some Child1 specific logic here
        }
    }

    public static void DoSomething(this Child2 myObj, Object dependency)
    {
        IMyInterface2 myInterface = dependency as IMyInterface2;
        if (myInterface != null)
        {
            //Do some Child2 specific logic here
        }
    }
}

更新:

这不起作用。它总是调用基类的扩展方法。是否有其他方法可以让我这样做,并避免必须显式检查运行时类型?原因是因为更多的类派生自 Base 可以添加,相应的扩展方法可以来自其他一些外部程序集。

提前致谢。


6159
2017-12-20 15:50


起源

你为什么不先试试,然后在必要时询问有什么问题? - Jon Skeet
好的会那样做。 - Kassem
那不行;扩展方法是静态分派的。 - SLaks
考虑使用访问者模式。 - SLaks
尝试使用 stackoverflow.com/questions/19958890/... - John Gibb


答案:


正如@SLaks已经声明你不能将该方法称为扩展方法(即使使用 dynamic type)...但是你可以用一个调用静态方法 dynamic 类型

所以,虽然这会失败

Base base1 = new Child1();
(base1 as dynamic).DoSomething();

这会奏效

Base base1 = new Child1();
Extensions.DoSomething(base1 as dynamic);

9
2017-12-20 16:07



这实际上有效!非常聪明我不得不说!所以底线是我可以使用静态方法实现它并按照您的建议调用它,但是无法使用在实例上调用的扩展方法来完成,对吧? - Kassem
@Kassem是的,这是我能在这里工作的唯一方法 - qujck
这真让我感到惊讶。 AFAIK,扩展方法(不能用于动态)由编译器转换为相同的静态方法调用(它可以工作)。在每种情况下都会看到IL会很有趣(但现在无法访问编译器)。任何人都可以解释为什么会这样吗? - Baldrick
这太棒了,值得更多的爱,谢谢 - Adam Marshall


不,那不行。

使用与重载解析相同的机制静态分派扩展方法。

如果你有一个编译时类型的变量 Base,无论运行时类型如何,编译器都将始终调用基本扩展方法。

相反,您可以使基本扩展方法检查运行时类型并调用适当的其他扩展方法。


4
2017-12-20 15:53



我试图避免检查运行时类型,因为更多的类派生自 Base 可以添加,相应的扩展方法可以来自其他一些外部程序集。 - Kassem


我刚才正在寻找同样的事情。

您可以向扩展类添加一个方法,如下所示:

public static void DoSomething(this Base myObj, Object dependency)
{       
    if(myObj.IsSubclassOf(Base))
    {
      // A derived class, call appropriate extension method.  
      DoSomething(myObj as dynamic, dependency);
    }
    else
    {
        // The object is Base class so handle it.
    }
} 

如果基类是抽象的(或从不在野外使用),则不需要if / else检查:

public static void DoSomething(this Base myObj, Object dependency)
{ 
    DoSomething(myObj as dynamic, dependency);
}

[编辑]实际上这不适用于您的情况,因为您没有实现对所有派生对象的支持(因此仍然可以获得无限递归)。我想你可以传递一些东西来检查递归,但给出的答案是最简单的。我会把它留在这里因为它可能引发更多的想法。


1
2018-03-16 16:35





下面是显示如何使用扩展方法模拟多态性的最小示例。

void Main()
{
    var elements = new Base[]{
        new Base(){  Name = "Base instance"},
        new D1(){    Name = "D1 instance"},
        new D2(){    Name = "D2 instance"},
        new D3(){    Name = "D3 instance"}

    };

    foreach(Base x in elements){
        x.Process();
    }
}

public class Base{
    public string Name;
}
public class D1 : Base {}
public class D2 : Base {}
public class D3 : Base {}


public static class Exts{

    public static void Process(this Base obj){
        if(obj.GetType() == typeof(Base)) Process<Base>(obj); //prevent infinite recursion for Base instances
        else Process((dynamic) obj);
    }

    private static void Process<T>(this T obj) where T: Base
    {
        Console.WriteLine("Base/Default: {0}", obj.Name);
    }

    public static void Process(this D1 obj){
        Console.WriteLine("D1: {0}", obj.Name);
    }

    public static void Process(this D2 obj){
        Console.WriteLine("D2: {0}", obj.Name);
    }
}

输出:

    Base/Default: Base instance
    D1: D1 instance
    D2: D2 instance
    Base/Default: D3 instance

1
2017-10-11 23:18





如果您不能使用关键字“dynamic”(旧版.NET),则可以使用反射来实现相同的功能。

取代:

Base base1 = new Child1();
Extensions.DoSomething(base1 as dynamic);

你可以写 :

Base base1 = new Child1();

MethodInfo method = typeof(Extensions).GetMethod("DoSomething", new System.Type[] { base1.GetType() });
if (method) {
    method.Invoke(new object[] { base1 });
}

0
2018-06-30 13:11