是否可以让 c# 使用大多数特定类型而不是基类型的方法重载?

Posted

技术标签:

【中文标题】是否可以让 c# 使用大多数特定类型而不是基类型的方法重载?【英文标题】:Is it possible to get c# to use method overload of most specific type rather than base type? 【发布时间】:2016-06-16 16:42:00 【问题描述】:

如果您有一个使用派生类型重载的方法,则在运行时调用的方法取决于变量的类型,即使底层对象实际上是派生类型:

class Program

    static void Main(string[] args)
    
        BaseClass theBaseObject = new BaseClass
        
            Foo = "FOO"
        ;
        DerivedClass theDerivedObject = new DerivedClass
        
            Foo = "FOO",
            Bar = "BAR"
        ;

        Processor processor = new Processor();

        Console.WriteLine(processor.Compose(theBaseObject));
        Console.WriteLine(processor.Compose(theDerivedObject));
        Console.WriteLine(processor.Compose((BaseClass) theDerivedObject));
    


public class Processor

    public string Compose(BaseClass item)
    
        return item.Foo;
    

    public string Compose(DerivedClass item)
    
        return Compose((BaseClass)item) + item.Bar;
    


public class BaseClass

    public string Foo  get; set; 


public class DerivedClass : BaseClass

    public string Bar  get; set; 

实际输出:

FOO
FOOBAR
FOO

我想找到一种方法来改变这种行为,以便为给定参数调用最具体的方法。

期望的输出:

FOO
FOOBAR
FOOBAR  // because theDerivedObject is an instance of DerivedClass 

这将允许对一堆项目进行特定处理,所有项目都来自一个基础。

这在 c# 中可行吗?


编辑:

澄清 - 实际上不会有显式转换,因为这些项目可能位于混合类型集合的列表中:

使用没有显式强制转换的列表的示例:

    foreach (BaseClass item in new [] theBaseObject, theDerivedObject )
    
        Console.WriteLine(processor.Compose(item));
    

实际输出:

FOO
FOO

期望的输出:

FOO
FOOBAR

显然演员阵容仍在进行中 - 但要移除它并不容易。

【问题讨论】:

当你将它转换为基本类型时,你实际上是在告诉它忽略它自己的任何属性 摆脱 (BaseClass) 演员表 我认为您可能对双重分派感兴趣,它利用了在运行时选择虚拟成员函数这一事实(而不是通过参数选择函数重载,这是在编译时解决的) ,正如你所展示的)。这个想法是您的 Processor.Compose() 方法只是“回调” argument 的虚函数(然后在运行时解析为最派生的实现)。这个乒乓球就是名字中“双”的原因。 @JNF 好吧,您是在告诉编译器选择哪个Compose()。您不会告诉接受强制转换的对象失去 any 属性;它没有改变。 @PeterA.Schneider,它可以在其他上下文中使用,但在这里它被视为基本类型。编译器不会以任何方式引用它 【参考方案1】:

这段代码让我想起了Haddocks' Eyes这首诗:

但我正在考虑一个计划 把胡须染成绿色, 并且总是使用这么大的风扇 他们看不见。

首先,您的代码创建了一个子类,然后将对象转换回基类,因此编译器看不到实际类型!您的示例中的重载在编译时解决,因此将调用 Compose(BaseClass item)

您可以扭转局面,让 .NET 通过隐藏所有重载并公开采用基类并执行动态转换的单个方法为您动态解决重载:

public class Processor 
    public string Compose(BaseClass item) 
        return ComposeImpl((dynamic)item);
    
    private string ComposeImpl(BaseClass item) 
        return item.Foo;
    
    private string ComposeImpl(DerivedClass item) 
        return ComposeImpl((BaseClass)item) + item.Bar;
    

“魔法”就在这一行:

return ComposeImpl((dynamic)item);

item 被强制转换为dynamic,这告诉系统必须根据BaseClass 对象的运行时类型来选择ComposeImpl 的实际重载。

【讨论】:

你的代码看起来很酷,但实际上它导致了 *** 异常 哇,太好了。所以dynamic 基本上可以将重载分辨率移动到运行时?在我看来,对于像 C++ 这样运行时支持少得多的编译语言来说,这似乎很难实现。 @DonaldJansen 啊,当然,我忘了把最后一次调用重命名为ComposeImpl 哈哈,我是这么想的,不过我从dynamic学到了一些东西 这是一个不错的解决方案,因为它不需要更改参数类(这都可以在处理器中完成),并且当您有很多派生类型时,您最终不会得到'If this then cast that' 类型逻辑的长列表【参考方案2】:

首先,您获得结果的原因是您特别声明了theDerivedObject as DerivedClass,因此它将DerivedClass作为参数,而(BaseClass) theDerivedObject)您基本上只是告诉编译器(如果我对编译器有误,请纠正我)或程序采用参数BaseClass的方法

您想要做的是以下选项之一

一号:

public class Processor

    public string Compose(BaseClass item)
    
        if (item is DerivedClass)
            return Compose((DerivedClass) item);
        return item.Foo;
    

    public string Compose(DerivedClass item)
    
        return item.Foo + item.Bar;
    

2号

public class Processor

    public string Compose(BaseClass item)
    
        if (item is DerivedClass)
        
            var derived = (DerivedClass) item;
            return derived.Foo + derived.Bar;
        
        return item.Foo;
    

或者你可能想要覆盖ToString()

public class Processor

    public string Compose(BaseClass item)
    
        var @class = item as DerivedClass;
        return @class?.ToString() ?? item.ToString();
    


public class BaseClass

    public string Foo  get; set; 

    public override string ToString()
    
        return Foo;
    


public class DerivedClass : BaseClass

    public string Bar  get; set; 

    public override string ToString()
    
        return Foo + Bar;
    

如果您没有使用 C#6.0,那么处理器将如下

public class Processor

    public string Compose(BaseClass item)
    
        var @class = item as DerivedClass;
        if (@class != null)
        
            return @class.ToString();
        
        return item.ToString();
    

【讨论】:

奇怪它为我工作,我得到他想要的结果作为所需的输出 哦,我明白了,你不使用 Compose(BaseClass item) 的调用 是的,否则会导致 *** 异常【参考方案3】:
public class Processor

    public string Compose(BaseClass item)
    
        return item.Compose();
    


public class BaseClass

    public string Foo  get; set; 

    public virtual string Compose()
    
        return Foo;
    


public class DerivedClass : BaseClass

    public string Bar  get; set; 

    public override string Compose()
    
        return base.Compose() + Bar;
    

【讨论】:

【参考方案4】:

难怪你得到你所得到的。你重载了Compose 方法。您声明了两个签名,一个用于基类,另一个用于派生。然后你多次调用该方法:

processor.Compose(theBaseObject) 致电Compose(BaseClass item) processor.Compose(theDerivedObject) 致电 Compose(DerivedClass item)。这里碰巧有几种方法可能适合调用。编译器选择一个在层次结构中具有最接近基类的方法,该方法本身就是DerivedClassprocessor.Compose((BaseClass) theDerivedObject)) 打电话给Compose(BaseClass item) 因为你告诉他这样做。它没有可供选择的选项。

您可能对类virtual 成员overriding 感兴趣。见

public class BaseClass

    public string Foo  get; set; 
    public virtual string GetComposeResult()  return Foo; 


public class DerivedClass : BaseClass

    public string Bar  get; set; 
    public override string GetComposeResult()  return Foo + Bar; 

在这种情况下,使用以下代码可以达到您的预期。

public class Processor

    public string Compose(BaseClass item)  return item.GetComposeResult(); 

【讨论】:

那是双重调度。我认为这是要走的路。

以上是关于是否可以让 c# 使用大多数特定类型而不是基类型的方法重载?的主要内容,如果未能解决你的问题,请参考以下文章

快速检测对象是不是完全是特定类型而不是该类型的子类

如何检查 Json 是不是匹配特定的 C# 类型?

如何将具有特定功能的对象定义为类型或接口?

c#基础 继承和接口,扩展类型

[C#]CLR via C#研习系列:动态基元类型和动态的C#

为啥我可以阻止基元而不是用户定义类型的隐式转换?