【问题标题】:Delegate With Null Instance Encounters Exception With Yield Return具有 Null 实例的委托遇到带有收益返回的异常
【发布时间】:2021-07-12 23:35:57
【问题描述】:

我在尝试从返回 IEnumerable 的函数创建委托时遇到了一些奇怪的行为。在前三个实例中,我可以传入一个空“this”并接收有效结果,但是在结构和产量返回的组合中,我遇到了运行时 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 来动态创建一个虚拟对象来解决这个问题。不过,我仍然对造成此问题的收益率回报有何不同感兴趣。

【问题讨论】:

  • 结构实例方法有一个隐藏的 byref this 参数。如果你传递 null (结构不能是)你会得到异常。 default() 之所以有效,是因为没有 no 结构,而是有一个 default 结构。您需要一个接受结构类型的单个 ref 参数的委托类型
  • @pinkfloydx33 我以为是这样的,谢谢。尽管从结构实例方法创建的第一个委托确实适用于空引用。出于某种原因,添加收益回报会引入问题。
  • 嗯... Yield return 在后台创建了一个状态机,这意味着它正在分配类来完成工作。可能是机器中的某些东西,然后从显示类或其他任何东西中取消引用该字段。

标签: c# class struct delegates


【解决方案1】:

使用yield returncreate a state machine的迭代器方法,这意味着包括this在内的局部变量被提升到隐藏类的字段中。

对于类的迭代器方法,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

为什么反射不抛出异常?因为它不是必须的。对象引用允许为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),因此Reflection 甚至允许调用有点令人惊讶。显然它总是可以做到的使用不安全的代码。

【讨论】:

  • 感谢您的深入解释。听起来这里实际的奇怪行为是在 TestStruct.testReturn 上也没有抛出异常。
  • @LFH 不,反射不需要进行空检查。 C# 使用call.virt 指令在调用站点自动执行此操作。令人惊讶的是ref指针为null,这是根本不允许的,见ECMA-335 Section II.14.4.2有进一步阐述
猜你喜欢
  • 2017-06-28
  • 2017-08-10
  • 1970-01-01
  • 1970-01-01
  • 2013-07-03
  • 2012-04-07
  • 1970-01-01
  • 1970-01-01
  • 2011-04-14
相关资源
最近更新 更多