具有 Null 实例的委托遇到 Yield Return 异常
Delegate With Null Instance Encounters Exception With Yield Return
我在尝试从 return 是 IEnumerable 的函数创建委托时遇到了一些奇怪的行为。在前三个实例中,我可以传入一个空“this”并收到有效结果,但是在结构和 yield return 的组合中,我遇到了运行时 NullReferenceException。请参阅下面的代码以重现该问题。
class Program
{
public delegate IEnumerable<int> test();
static void Main(string[] args)
{
var method2 = typeof(TestClass).GetMethod("testReturn");
var test2 = (test)Delegate.CreateDelegate(typeof(test), null, method2);
var results2 = test2.Invoke();
Console.WriteLine("This works!");
var method = typeof(TestClass).GetMethod("testYield");
var test = (test)Delegate.CreateDelegate(typeof(test), null, method);
var results = test.Invoke();
Console.WriteLine("This works!");
var method3 = typeof(TestStruct).GetMethod("testReturn");
var test3 = (test)Delegate.CreateDelegate(typeof(test), null, method3);
var results3 = test3.Invoke();
Console.WriteLine("This works!");
var method4 = typeof(TestStruct).GetMethod("testYield");
var test4 = (test)Delegate.CreateDelegate(typeof(test), null, method4);
var results4 = test4.Invoke();
Console.WriteLine("This doesn't work...");
}
public class TestClass
{
public IEnumerable<int> testYield()
{
for (int i = 0; i < 10; i++)
yield return i;
}
public IEnumerable<int> testReturn()
{
return new List<int>();
}
}
public struct TestStruct
{
public IEnumerable<int> testYield()
{
for (int i = 0; i < 10; i++)
yield return i;
}
public IEnumerable<int> testReturn()
{
return new List<int>();
}
}
}
当我传入 default(TestStruct) 而不是 null 时,它 确实 工作,但是我将无法在运行时以这种方式引用正确的类型。
编辑:我能够通过使用 Activator.CreateInstance 而不是 null 来动态创建一个虚拟对象来解决这个问题。不过,我仍然对造成此问题的产量 return 有何不同感兴趣。
使用 yield return
create a state machine 的迭代器方法,这意味着包括 this
在内的局部变量被提升到隐藏 class.[=33= 的字段中]
对于 classes 的迭代器方法,this
显然是一个对象引用。但是对于结构,this
是结构的 ref
。
查看 Sharplab 中生成的编译器,您可以了解为什么 TestStruct.testYield
失败而不是 TestClass.testYield
。
TestClass.testYield
对其 this
参数的唯一引用是:
IL_0008: ldarg.0
IL_0009: stfld class C/TestClass C/TestClass/'<testYield>d__0'::'<>4__this'
这 不 涉及对 this
的取消引用,在您的情况下是 null
.
为什么Reflection不抛出异常?因为不需要这样做。对象引用允许是 null
,即使它是 this
参数。 C# 将引发直接调用,因为它总是生成 callvirt
指令。
而 TestStruct.testYield
实际上取消引用其 this
参数, 这是因为将 ref struct
提升为领域:
IL_0008: ldarg.0
IL_0009: ldobj C/TestStruct
IL_000e: stfld valuetype C/TestStruct C/TestStruct/'<testYield>d__0'::'<>3__<>4__this'
从技术上讲,托管 ref
指针不允许为空(参见 ECMA-335 Section II.14.4.2),因此反射甚至允许调用有点令人惊讶。 显然它总是可以用不安全的代码来完成。
我在尝试从 return 是 IEnumerable 的函数创建委托时遇到了一些奇怪的行为。在前三个实例中,我可以传入一个空“this”并收到有效结果,但是在结构和 yield return 的组合中,我遇到了运行时 NullReferenceException。请参阅下面的代码以重现该问题。
class Program
{
public delegate IEnumerable<int> test();
static void Main(string[] args)
{
var method2 = typeof(TestClass).GetMethod("testReturn");
var test2 = (test)Delegate.CreateDelegate(typeof(test), null, method2);
var results2 = test2.Invoke();
Console.WriteLine("This works!");
var method = typeof(TestClass).GetMethod("testYield");
var test = (test)Delegate.CreateDelegate(typeof(test), null, method);
var results = test.Invoke();
Console.WriteLine("This works!");
var method3 = typeof(TestStruct).GetMethod("testReturn");
var test3 = (test)Delegate.CreateDelegate(typeof(test), null, method3);
var results3 = test3.Invoke();
Console.WriteLine("This works!");
var method4 = typeof(TestStruct).GetMethod("testYield");
var test4 = (test)Delegate.CreateDelegate(typeof(test), null, method4);
var results4 = test4.Invoke();
Console.WriteLine("This doesn't work...");
}
public class TestClass
{
public IEnumerable<int> testYield()
{
for (int i = 0; i < 10; i++)
yield return i;
}
public IEnumerable<int> testReturn()
{
return new List<int>();
}
}
public struct TestStruct
{
public IEnumerable<int> testYield()
{
for (int i = 0; i < 10; i++)
yield return i;
}
public IEnumerable<int> testReturn()
{
return new List<int>();
}
}
}
当我传入 default(TestStruct) 而不是 null 时,它 确实 工作,但是我将无法在运行时以这种方式引用正确的类型。
编辑:我能够通过使用 Activator.CreateInstance 而不是 null 来动态创建一个虚拟对象来解决这个问题。不过,我仍然对造成此问题的产量 return 有何不同感兴趣。
使用 yield return
create a state machine 的迭代器方法,这意味着包括 this
在内的局部变量被提升到隐藏 class.[=33= 的字段中]
对于 classes 的迭代器方法,this
显然是一个对象引用。但是对于结构,this
是结构的 ref
。
查看 Sharplab 中生成的编译器,您可以了解为什么 TestStruct.testYield
失败而不是 TestClass.testYield
。
TestClass.testYield
对其 this
参数的唯一引用是:
IL_0008: ldarg.0
IL_0009: stfld class C/TestClass C/TestClass/'<testYield>d__0'::'<>4__this'
这 不 涉及对 this
的取消引用,在您的情况下是 null
.
为什么Reflection不抛出异常?因为不需要这样做。对象引用允许是 null
,即使它是 this
参数。 C# 将引发直接调用,因为它总是生成 callvirt
指令。
而 TestStruct.testYield
实际上取消引用其 this
参数, 这是因为将 ref struct
提升为领域:
IL_0008: ldarg.0
IL_0009: ldobj C/TestStruct
IL_000e: stfld valuetype C/TestStruct C/TestStruct/'<testYield>d__0'::'<>3__<>4__this'
从技术上讲,托管 ref
指针不允许为空(参见 ECMA-335 Section II.14.4.2),因此反射甚至允许调用有点令人惊讶。 显然它总是可以用不安全的代码来完成。