通用直通接口.这可能吗?

本文关键字:直通 接口 | 更新日期: 2023-09-27 18:13:04

我愿意付出一切来换取这份工作:

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特定的,另一个是开放式的)。嗯,我实际上希望它会认为这是"相同的签名",这样两者都能满足接口,并且在运行时选择"最佳"。啊好。

实际上,我试图实现的是模糊类似于"特征",这是c++的"else-if-then"。我想它也可以被认为是"接口签名逆变"的一种形式。

我很想发现有一个特殊的c#关键字可以实现这个功能,或者它是c#在。net 4.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中测试上述代码。

试着改变你的Worker类:

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

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

我也提请你注意这里的评论和其他答案。我建议你读一读,并理解为什么这样做可能不是一个好主意。

此外,如下面的答案所述,如果显式实现Handle()方法,将参数类型限制为ICallBack 仍然会导致运行时错误

下面是这个看起来很简单的方法的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 
}