通用传递接口。这可能吗?

时间:2013-05-09 14:38:05

标签: c# generics

我不愿意让这项工作:

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)

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

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

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

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

实际上,我试图实现与“特征”模糊地相似,即“else-if-then of C++”。我想它也可以被认为是“界面签名逆变”的一种形式。

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

是的,不是吗?评论

2 个答案:

答案 0 :(得分: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中测试上述代码。

答案 1 :(得分: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 
}