3

我不会放弃这项工作:

public interface ICallBack
{
    void Handle<T>(T arg);
}

public class MessageHandler : ICallBack
{
    public void Handle<T>(T arg)
    {
        string name = typeof(T).Name;
        Console.WriteLine(name);
    }

    public void Handle(int arg)
    {
        string name = "wow, an int";
        Console.WriteLine(name);
    }
}

public class Worker
{
    public void DoSomething(ICallBack cb)
    {
        cb.Handle(55);
    }
}

//...
Worker foo = new Worker();
ICallBack boo = new MessageHandler();

//I want this to print "Wow, an int"
foo.DoSomething(boo)

不幸的是,它调用的是通用入口点而不是“专用”入口点。嗯,这就是你的接口。

我也尝试过相同的方法,但用特定的通用签名替换特定于 int 的签名Mojo

public void Handle<T>(T arg) where T : Mojo {}

如果参数是 type ,我希望这足以形成“特殊覆盖” Mojo。但是现在编译器抱怨我有两个具有相同签名的方法(一个是Mojo特定的,另一个是开放式的)。好吧,我实际上希望它会认为它是“相同的签名”,以便两者都能满足接口并且在运行时选择“最佳”。呃,好吧。

实际上,我试图实现的目标与“Traits”隐约相似,后者是“ C++ 的 else-if-then ”。我想它也可以被认为是“接口签名逆变”的一种形式。

我很想发现有一个特殊的 C# 关键字可以启用此功能,或者它是 .net 4.5 中 C# 的特色补充。

是的,不是吗?评论?

4

2 回答 2

5

不,这是不可能的。

当编译器编译实现接口的类型时,它将创建一个接口映射,详细说明链接到接口的每个方法的类型的哪些方法。这不能在运行时随意更改。

这意味着无论何时您Handle通过该接口调用您的方法,它总是会转到底层类型上的相同方法,而不管您认为应该更合适的任何其他方法。

如果您希望底层类型在内部调用特定方法,具体取决于泛型参数的特定类型,您必须自己实现它,或者使用动态调度,或者使用 if 语句或类似的方法来检测您使用哪种类型的 T拥有并调用适当的方法。

这里的答案说您可以将调用该方法的类型强制转换为dynamic意味着您正在使用反射来完全绕过接口。对于这个特定场景,接口可能根本没有任何方法,转换dynamic仍然会“工作”。

我不推荐这种方法。您正在有效地编写代码,假设它可以全权访问底层类型的所有方法,即使它明确表示“我只需要这个接口”。

此外,如果唯一的目标是避免运行时错误,请考虑如果在类中显式实现该方法会发生什么:

void Main()
{
    Worker foo = new Worker();
    ICallBack boo = new MessageHandler();

    foo.DoSomething(boo);
}

public interface ICallBack
{
    void Handle<T>(T arg);
}

public class MessageHandler : ICallBack
{
    void ICallBack.Handle<T>(T arg)
    {
        string name = typeof(T).Name;
        Console.WriteLine(name);
    }
}

public class Worker
{
    public void DoSomething(ICallBack cb)
    {
        ((dynamic)cb).Handle(55);
    }
}

这将在运行时崩溃:

RuntimeBinderException
“UserQuery.MessageHandler”不包含“Handle”的定义

您可以在LINQPad中测试上述代码。

于 2013-05-09T15:07:20.003 回答
3

尝试将您的Worker课程更改为:

public class Worker
{
    public void DoSomething(ICallBack cb)
    {
        ((dynamic)cb).Handle(55);
    }
}

[编辑]

正如您所知,添加看起来无害的“动态”会严重改变输出代码。它在运行时有效地调用编译器来做动态的事情。

我还提请您注意这里的评论和其他答案。我建议您阅读它们并理解为什么执行上述操作可能不是一个好主意。

此外,如以下答案中所述,如果显式实现该方法,则将参数类型限制为ICallBack 仍将允许运行时错误。Handle()

这是那个看起来很简单的方法的 IL:

.method public hidebysig instance void DoSomething(class ConsoleApplication1.ICallBack cb) cil managed
{
    .maxstack 9
    .locals init (
        [0] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)
    L_0000: nop 
    L_0001: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>> ConsoleApplication1.Worker/<DoSomething>o__SiteContainer0::<>p__Site1
    L_0006: brtrue L_0058
    L_000b: ldc.i4 256
    L_0010: ldstr "Handle"
    L_0015: ldnull 
    L_0016: ldtoken ConsoleApplication1.Worker
    L_001b: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0020: ldc.i4.2 
    L_0021: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
    L_0026: stloc.0 
    L_0027: ldloc.0 
    L_0028: ldc.i4.0 
    L_0029: ldc.i4.0 
    L_002a: ldnull 
    L_002b: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
    L_0030: stelem.any [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
    L_0035: ldloc.0 
    L_0036: ldc.i4.1 
    L_0037: ldc.i4.3 
    L_0038: ldnull 
    L_0039: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
    L_003e: stelem.any [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
    L_0043: ldloc.0 
    L_0044: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
    L_0049: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
    L_004e: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>> ConsoleApplication1.Worker/<DoSomething>o__SiteContainer0::<>p__Site1
    L_0053: br L_0058
    L_0058: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>> ConsoleApplication1.Worker/<DoSomething>o__SiteContainer0::<>p__Site1
    L_005d: ldfld !0 [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>>::Target
    L_0062: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>> ConsoleApplication1.Worker/<DoSomething>o__SiteContainer0::<>p__Site1
    L_0067: ldarg.1 
    L_0068: ldc.i4.s 12
    L_006a: callvirt instance void [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, int32>::Invoke(!0, !1, !2)
    L_006f: nop 
    L_0070: ret 
}
于 2013-05-09T14:43:03.557 回答