当知道属性时,为什么不使用`dynamic`而不是反射?

时间:2018-01-05 07:30:26

标签: c# dynamic reflection

这个问题类似于this one,但假设我们在编译时知道成员名称。

假设我们有一个班级

public class MyClass
{
    public string TheProperty { get; set; }
}

并且在另一种方法中,我们想要设置该类实例的TheProperty成员,但是我们在编译时不知道实例的类型,我们只知道属性名称at编译时间。 所以,正如我所看到的,现在有两种方法可以做到这一点:

object o = new MyClass(); // For simplicity.

o.GetType().GetProperty("TheProperty").SetValue(o, "bar"); // (1)    
((dynamic) o).TheProperty = "bar"; // (2)

我使用System.Diagnostics.Stopwatch类测量了这个测试用例,发现反射需要475个刻度,使用 dynamic采用0刻度的方式因此,与直接调用new MyClass().TheProperty = "bar"一样快。

由于我几乎从未见过第二种方式,我有点困惑,现在我的问题是:

  • 是否有想法或其他什么?
  • 第二种方式应该优先于第一种方式还是其他方式?我没有看到使用第二种方式的任何缺点;如果找不到财产,(1)和(2)都会抛出异常,不是吗?
  • 为什么第二种方式似乎很少使用,即使看起来更快?

1 个答案:

答案 0 :(得分:3)

  

(...)反射需要475个刻度,使用动态的方式需要0个刻度(...)

这简直是假的。问题是你不了解dynamic的工作原理。我会假设您正确设置基准:

  1. 在发布模式下运行,并且启用了优化并且没有调试器。
  2. 在实际测量时间之前,您正在使用这些方法。
  3. 这是你可能没有做的关键部分:

    1. 在不实际执行动态运行时绑定的情况下Jit动态测试
    2. 为什么3重要?因为运行时将缓存动态调用并重用它!因此,在一个天真的基准测试实现中,如果你正在做正确的事情,你将承担初始动态调用的成本,因此你不会测量它。

      运行以下基准:

      public static void Main(string[] args)
      {
          var repetitions = 1;
          var isWarmup = true;
          var foo = new Foo();
      
          //warmup
          SetPropertyWithDynamic(foo, isWarmup); //JIT method without caching the dynamic call
          SetPropertyWithReflection(foo); //JIT method
          var s = ((dynamic)"Hello").Substring(0, 2); //Start up the runtime compiler
      
          for (var test = 0; test < 10; test++)
          {
              Console.WriteLine($"Test #{test}");
              var watch = Stopwatch.StartNew();
      
              for (var i = 0; i < repetitions; i++)
              {
                  SetPropertyWithDynamic(foo);
              }
      
              watch.Stop();
              Console.WriteLine($"Dynamic benchmark: {watch.ElapsedTicks}");
      
              watch = Stopwatch.StartNew();
      
              for (var i = 0; i < repetitions; i++)
              {
                  SetPropertyWithReflection(foo);
              }
      
              watch.Stop();
              Console.WriteLine($"Reflection benchmark: {watch.ElapsedTicks}");
          }
      
          Console.WriteLine(foo);
          Console.ReadLine();
      }
      
      static void SetPropertyWithDynamic(object o, bool isWarmup = false)
      {
          if (isWarmup)
              return;
      
          ((dynamic)o).TheProperty = 1;
      }
      
      static void SetPropertyWithReflection(object o)
      {
          o.GetType().GetProperty("TheProperty").SetValue(o, 1);
      }
      
      public class Foo
      {
          public int TheProperty { get; set; }
          public override string ToString() => $"Foo: {TheProperty}";
      }
      

      发现第一次运行和后续运行之间的区别?