【问题标题】:c# multiple dispatch options?c#多个调度选项?
【发布时间】:2012-05-07 00:45:13
【问题描述】:

我有这些课程:

class Asset
{    }

class House:Asset
{    }

考虑这些局外人静态函数:

static void Foo (Asset a) { }
static void Foo (House h) { }

如果我写:

House h = new House (...); 
Foo(h);

它将调用Foo(House)(编译时绑定)

如果我写:

Asset a = new House (...);
Foo(a); 

它将调用Foo(Asset)(编译时绑定)

目标:访问运行时类型方法:

我有两个选择:

1) 像这样使用动态:

 Asset a = new House (...);
 Foo ((dynamic)a); // NOW it will call  Foo(House)

2) 使用polymorphism mechanism 将函数从static 移动到override

问题

是否有任何其他方法(无需将函数移至polymorphism mechanism || dynamic)?

【问题讨论】:

  • 资产 a = 新房子 (...);不会调用 Foo(Asset),因为 runtime 类型是 House,所以会调用 Foo(House)
  • @Tigran:在编译时执行重载解析。 Asset a = new House(...); Foo(a); 将调用 Foo(Asset),因为 a 被声明为 Asset
  • @Tigran:有两种名为 Foo 的方法,它们的参数列表不同。 Overloading “当你有两个同名但签名不同的方法时会发生这种情况。在编译时,编译器会根据参数的编译时类型和方法的目标来计算出它将调用哪个方法称呼。”如果Asset 中有一个实例方法virtual void FooHouse 中有一个实例方法override void Foo覆盖),情况就不同了。
  • @Tigran:这是一个简短但完整的示例程序:class Asset { } class House : Asset { } class Program { static void Main() { Asset a = new House(); Foo(a); } static void Foo(Asset a) { Console.WriteLine("Asset"); } static void Foo(House h) { Console.WriteLine("House"); }。它打印Asset。因为没有调度是运行时的。只需在编译时重载解析。
  • @MichaelBuen:神奇之处在于:编译器发出的代码在运行时再次启动 C# 编译器。编译器的运行时版本分析调用好像所有对象的编译时类型都是它们的实际运行时类型,生成表示该调用的表达式树,编译表达式树,缓存下一次委托,并运行委托。您第二次点击此调用站点时,它只会调用委托。

标签: c# dynamic .net-4.0 multiple-dispatch


【解决方案1】:

目标:访问运行时类型方法

这就是dynamic 关键字的用途。这实际上是一种非常干净且快速的多次调度方式。

Multiple Dispatch 的终极选择是

  1. dynamic
  2. 双重调度虚拟方法
  3. 一些散列匿名函数规则集合
  4. if (x is House) ... else if(x is Asset)...
  5. 反射 -- 真的很慢很丑

问题:还有其他方法吗(不将函数移动到多态机制||动态)?

所以是的,当您可以使用 dynamic 快速、不易出错且真正的 clean syntax wise 时,有很多方法需要您做很多工作。

【讨论】:

【解决方案2】:

如果你想要静态入口点,但你也想要多态行为,那么最简单的混合就是使用单例模式。它将为您提供静态入口点,并且它返回的对象将具有多态方法。

我还建议忽略所有说单身是一件可怕的坏事的人。 Prima Donna 单例讲道是中级开发人员的陈词滥调。单例只是一种工具,如果它符合您的需求,那就使用它。

【讨论】:

    【解决方案3】:

    我认为这就是 Foo((dynamic)a) 背后发生的事情:

    Asset a = new House();
    
    Type t = typeof(MainClass);
    t.InvokeMember("Foo", 
         System.Reflection.BindingFlags.InvokeMethod, null, 
         t, new object[] { a });
    

    这将解析为Foo(House h)


    快速访问 monodis.exe,不使用反射(例如 InvokeMember),即使用动态关键字 Asset a = new House(); Foo((dynamic)a),这是 IL:

    IL_0025:  ldstr "Foo"
    IL_002a:  ldnull 
    IL_002b:  ldtoken MainClass
    IL_0030:  call class [mscorlib]System.Type class [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    

    几乎你的直觉会告诉你,“Foo”是一个死的赠品,动态是反射-y类型的业务。

    现在,这是没有动态的,即Asset a = new House(); Foo(a)

    IL_0010:  ldloc.0 
    IL_0011:  call void class MainClass::Foo(class Asset)
    

    烧毁的指令几乎是确定的,不会改变,它总是解析为Foo(Asset);

    以下是可用于分析动态行为的完整代码(通过 monodis.exe 或 ildasm.exe):

    using System;
    
    public class MainClass {
        public static void Main() {
    
            Console.WriteLine("Hei");
    
            Asset a = new House();        
            Foo(a);    
            Foo((dynamic)a);  
    
            object x = 7;
            Foo((dynamic)x);
        }
    
        public static void Foo(House h) { Console.WriteLine("House"); }
        public static void Foo(Asset a) { Console.WriteLine("Asset"); }
        public static void Foo(int i) { Console.WriteLine("int"); }
    }
    
    
    public class Asset {
    }
    
    public class House : Asset {
    }
    

    输出:

    Hei
    Asset
    House
    int
    

    这将调用 Foo 重载 int,即 Foo(int i):

    object x = 7;
    t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, 
       t, new object[] { x } ); 
    

    这也是:

    t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, 
       t, new object[] { 8 } ); 
    

    所以关于你的问题,你可以使用什么其他选项,你可以使用一个接受无类型对象的方法:

    public static void FooDynamic(object o) 
    {
        Type t = typeof(MainClass);
        t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, t, new object[] { o } ); 
    }
    

    调用:

    Asset a = new House();
    FooDynamic(a); // will select Foo House overload 
    
    int i = 7;
    FooDynamic(i); // will select Foo int overload
    

    您也可以将此 API 用于上面的代码:public static void Foo(object o),然后您必须像这样调用 Foo:

    Asset a = new House();
    Foo((object)a); // will resolve to House
    

    鉴于 C# 4 中已经有 dynamic 功能,除非开发人员仍在使用 C# 3,否则我将很难使用反射。因此,请改用动态方法 :-)


    更新

    对于它的价值,dynamicslow(至少在 Mono 上),当我运行此代码时,字母“B”出现之前有相当长的延迟,大约 2 秒。即使我交换动态和反射的代码顺序,动态的延迟也是可重现的。反射的延迟是难以察觉的,它比动态的要快。

    using System;
    
    public class MainClass {
    
        public static void Main() {
    
            // there's a delay on initial dynamic call, about two seconds
            Test (); 
            Console.ReadLine ();
    
            // dynamic's speed is instant on subsequent calls, 
            // as clarified by Eric Lippert, the delegate is cached,
            // hence the elimination of delay on subsequent dynamic calls
            Test (); 
    
        }
    
        public static void Test() {
    
            Asset a = new House();
    
            Console.WriteLine("A");
            Foo((dynamic)a);  // there is a considerable delay here, the "B" string appears after two seconds
    
            Console.WriteLine ("B");        
            Type t = typeof(MainClass);
            t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, t, new object[] { a } ); 
    
            Console.WriteLine("C");
    
        }
    
    
        public static void Foo(House h) { Console.WriteLine("House"); }
        public static void Foo(Asset a) { Console.WriteLine("Asset"); }
        public static void Foo(int i) { Console.WriteLine("int"); }
    }
    
    
    public class Asset {
    }
    
    public class House : Asset {
    }
    

    【讨论】:

    • 在引擎盖下,编译器正在做一些疯狂的事情来解析静态方法,但使用运行时类型,动态编译并缓存它以供将来使用。实际上,您的反射示例不适用于重载方法,您缺少用于选择正确重载的活页夹。
    • 相反,它可以工作:-) InvokeMember 负责处理要调用的重载方法的业务。查看我的编辑
    • 对不起,你是对的,InvokeMember 在这种情况下工作,在更复杂的情况下它不工作。但是Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember 是编译器在您使用dynamic 时实际换出的内容,它比反射方法处理更多的情况,并且在运行时发出 IL 以使其有时比反射快 100 倍。
    • @jbtule 使用dynamic(至少在 Mono C# 上)有明显的延迟,大约两秒;在我的测试中,反射是即时的,比动态更快。请参阅上面的更新。明天我将在 Microsoft C# 上测试相同的代码
    猜你喜欢
    • 2018-04-08
    • 2022-07-16
    • 2022-01-20
    • 2010-09-08
    • 2019-08-04
    • 1970-01-01
    • 2022-11-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多