微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

带有空实例的委托遇到异常并返回收益

如何解决带有空实例的委托遇到异常并返回收益

我在尝试从返回 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),method);
            var results = test.Invoke();
            Console.WriteLine("This works!");
 
            var method3 = typeof(TestStruct).getmethod("testReturn");
            var test3 = (test)Delegate.CreateDelegate(typeof(test),method3);
            var results3 = test3.Invoke();
            Console.WriteLine("This works!");
 
            var method4 = typeof(TestStruct).getmethod("testYield");
            var test4 = (test)Delegate.CreateDelegate(typeof(test),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 来动态创建虚拟对象来解决这个问题。不过,我仍然对产生此问题的收益率的不同之处感兴趣。

解决方法

使用 yield return create 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),因此反射甚至允许调用有点令人惊讶。 显然它总是可以用不安全的代码来完成。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。