区块链技术博客
www.b2bchain.cn

如何在IL中实现C#foreach优化 – c#程序员分享

本文介绍了如何在IL中实现C#foreach优化 – c#程序员分享,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

在此answer和此GitHub issue(顶部)中,描述了C#编译器使用的foreach优化。

基本上,而不是分配IEnumerable<T>,生成的代码始终在返回的对象上调用GetEnumerator(),然后调用MoveNext(),始终使用直接的call,因此避免装箱和虚拟调用。

是否可以用中间语言编写相同的逻辑?我是IL的初学者,但是熟悉Unsafe package及其工作方式。我想知道是否有可能在IL中编写一个不安全的方法来接受某些对象并直接将其称为方法和属性吗?

(此外,有人可以链接到Roslyn repo中发生此foreach优化的行吗?回购是如此之大和复杂,到目前为止我迷失了。)

更新:

这是一个方法模板

[MethodImpl(MethodImplOptions.AggressiveInlining)] [ILSub(@"     .. IL code here to be replaced by ilasm.exe     .. Is there a way to do the same without boxing and virtual calls?     ")] public T CallIEnumerableMoveNextViaIL<T>(IEnumerable<T> enumerable) {     // I know that the `enumerable` returns an enumerator that is a struct, but its type could be custom     // Next two calls are virtual via an interface, and enumerator is boxed     var enumerator = enumerable.GetEnumerator();     enumerator.MoveNext();     return enumerator.Current; } 

这是该方法的IL:

IL_0000: ldarg.1 IL_0001: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!!T>::GetEnumerator() IL_0006: dup IL_0007: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() IL_000c: pop IL_000d: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<!!T>::get_Current() IL_0012: ret 

从注释看来,在不知道类型的情况下不可能调用get_Current()之类的方法。

参考方案

让我们举几个有关如何编译foreach的示例。
首先,在返回IEnumerator的常规GetEnumerator上:

public class A : IEnumerable {     public IEnumerator GetEnumerator()     {         throw new NotImplementedException();     } }  foreach(object o in new A()) {  } 
  .locals init (class [mscorlib]System.Collections.IEnumerator V_0,            class [mscorlib]System.IDisposable V_1)   IL_0000:  newobj     instance void Testing.Program/A::.ctor()   IL_0005:  call       instance class [mscorlib]System.Collections.IEnumerator Testing.Program/A::GetEnumerator()   IL_000a:  stloc.0   .try   {     IL_000b:  br.s       IL_0014     IL_000d:  ldloc.0     IL_000e:  callvirt   instance object [mscorlib]System.Collections.IEnumerator::get_Current()     IL_0013:  pop     IL_0014:  ldloc.0     IL_0015:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()     IL_001a:  brtrue.s   IL_000d     IL_001c:  leave.s    IL_002f   }  // end .try   finally   {     IL_001e:  ldloc.0     IL_001f:  isinst     [mscorlib]System.IDisposable     IL_0024:  stloc.1     IL_0025:  ldloc.1     IL_0026:  brfalse.s  IL_002e     IL_0028:  ldloc.1     IL_0029:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()     IL_002e:  endfinally   }  // end handler 

这里没什么奇妙的,只是在IEnumerator上调用方法。请注意,它还实现了IDisposable,因此使用其模式。

接下来,让我们有一个返回GetEnumerator的值类型:

public class B {     public BE GetEnumerator()     {         return new BE();     }      public struct BE     {         public object Current {             get {                 throw new NotImplementedException();             }         }          public bool MoveNext()         {             throw new NotImplementedException();         }          public void Reset()         {             throw new NotImplementedException();         }     } } 
  .locals init (class [mscorlib]System.Collections.IEnumerator V_0,            class [mscorlib]System.IDisposable V_1,            valuetype Testing.Program/B/BE V_2,            object[] V_3,            int32 V_4)   IL_002f:  newobj     instance void Testing.Program/B::.ctor()   IL_0034:  call       instance valuetype Testing.Program/B/BE Testing.Program/B::GetEnumerator()   IL_0039:  stloc.2   IL_003a:  br.s       IL_0044   IL_003c:  ldloca.s   V_2   IL_003e:  call       instance object Testing.Program/B/BE::get_Current()   IL_0043:  pop   IL_0044:  ldloca.s   V_2   IL_0046:  call       instance bool Testing.Program/B/BE::MoveNext()   IL_004b:  brtrue.s   IL_003c 

请注意,此处无需实现IEnumerable / IEnumerator接口。这种方法有时称为鸭子输入法。此实现在内部将枚举数存储在变量中,然后在其地址(ldloca)上调用方法。从GetEnumerator返回枚举数时,将在此处进行一种值类型复制。

第三个例子几乎是完全不同的东西,数组上的foreach

foreach(object o in new object[0]) {  }    .locals init (class [mscorlib]System.Collections.IEnumerator V_0,            class [mscorlib]System.IDisposable V_1,            valuetype Testing.Program/B/BE V_2,            object[] V_3,            int32 V_4)  IL_004d:  ldc.i4.0   IL_004e:  newarr     [mscorlib]System.Object   IL_0053:  stloc.3   IL_0054:  ldc.i4.0   IL_0055:  stloc.s    V_4   IL_0057:  br.s       IL_0064   IL_0059:  ldloc.3   IL_005a:  ldloc.s    V_4   IL_005c:  ldelem.ref   IL_005d:  pop   IL_005e:  ldloc.s    V_4   IL_0060:  ldc.i4.1   IL_0061:  add   IL_0062:  stloc.s    V_4   IL_0064:  ldloc.s    V_4   IL_0066:  ldloc.3   IL_0067:  ldlen   IL_0068:  conv.i4   IL_0069:  blt.s      IL_0059 

这不使用GetEnumerator,而只是以老式的索引方式遍历数组(更高的性能)。

您不能在CIL中使用这种精确的优化,因为CIL没有鸭子类型。您必须自己编写所有签名和方法调用。

但是,如果需要针对任何类型进行此优化,并且可以修改要使用的类型,则可以在类似于以下代码的代码中使用通用接口:

public class B : IStructEnumerable<object, BE> {     public BE GetEnumerator()     {         return new BE();     } }  public struct BE : IStructEnumerator<object> {     public object Current {         get {             throw new NotImplementedException();         }     }      public bool MoveNext()     {         throw new NotImplementedException();     }      public void Reset()     {         throw new NotImplementedException();     } }  public interface IStructEnumerable<TItem, TEnumerator> where TEnumerator : struct, IStructEnumerator<TItem> {     TEnumerator GetEnumerator(); }  public interface IStructEnumerator<TItem> {     TItem Current {get;}     bool MoveNext();     void Reset(); }  public static void TestEnumerator<TEnumerable, TEnumerator>(TEnumerable b) where TEnumerable : IStructEnumerable<object, TEnumerator> where TEnumerator : struct, IStructEnumerator<object> {     foreach(object obj in b)     {      } } 

在判断此问题已得到回答之前,请阅读说明。我在下面有这个简单的代码:Dictionary<string, object> d = new Dictionary<string, object>(); d.Add("key" , 30d); System.Diagnostics.Debug.WriteLine($&#03…

当我从Eclipse Helios使用System.console时,它总是返回null。但是,当我直接从命令行使用它时(即从命令提示符手动编译并执行Java源代码),我确实获得了一个Console对象。要知道为什么会这样,所以我检查了this链接。据此,当我从Eclipse运行Java代码时,后台作业调度程序必须正在启动我的JVM。这是什么意思?从命令行启…

我已经完成了完美的编码注册页面,登录代码,现在UpdateCustomer页面有错误-背景信息:我正在使用Microsoft Access作为数据源 LabelState.Text = (string)Session["sState"]; LabelPostalCode.Text = (string)Session["sPost…

我需要一个每分钟执行一次的计时器,但是我很难让计时器完全用我以前使用的代码运行。所以我想我在做一些根本上与代码无关的错误,但是即使在Visual Studio Community 2017中刚刚创建的Console项目中,它也不会执行_timer_elapsed方法。控制台立即终止,没有错误,就好像它已经执行了所有代码using System; using …

我正在创建游戏,并且具有寻路功能,该功能大约需要100毫秒。我有5个敌人,每个都在构造函数中具有此功能:newPath = new System.Threading.Timer((e) => { getNewPath(); //The function that takes ~100 ms }, null, 0, 5000); 现在,我在程序的较早位置…

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 如何在IL中实现C#foreach优化 – c#程序员分享
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们