C# - 初始化器内类字段的闭包?

发布于 2024-08-25 15:14:27 字数 964 浏览 4 评论 0原文

考虑以下代码:(

using System;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var square = new Square(4);
            Console.WriteLine(square.Calculate());
        }
    }

    class MathOp
    {        
        protected MathOp(Func<int> calc) { _calc = calc; }
        public int Calculate() { return _calc(); }
        private Func<int> _calc;
    }

    class Square : MathOp
    {
        public Square(int operand)
            : base(() => _operand * _operand)  // runtime exception
        {
            _operand = operand;
        }

        private int _operand;
    }
}

忽略类设计;我实际上并没有编写计算器!此代码仅代表一个需要一段时间才能缩小范围的更大问题的最小重现)

我希望它能够:

  • 打印“16” ,或者
  • 如果在这种情况下不允许关闭成员字段,则抛出编译时错误

相反,我在指定的行抛出一个无意义的异常。在 3.0 CLR 上,它是一个 NullReferenceException;在 Silverlight CLR 上,这是臭名昭著的操作可能会破坏运行时的稳定性。

Consider the following code:

using System;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var square = new Square(4);
            Console.WriteLine(square.Calculate());
        }
    }

    class MathOp
    {        
        protected MathOp(Func<int> calc) { _calc = calc; }
        public int Calculate() { return _calc(); }
        private Func<int> _calc;
    }

    class Square : MathOp
    {
        public Square(int operand)
            : base(() => _operand * _operand)  // runtime exception
        {
            _operand = operand;
        }

        private int _operand;
    }
}

(ignore the class design; I'm not actually writing a calculator! this code merely represents a minimal repro for a much bigger problem that took awhile to narrow down)

I would expect it to either:

  • print "16", OR
  • throw a compile time error if closing over a member field is not allowed in this scenario

Instead I get a nonsensical exception thrown at the indicated line. On the 3.0 CLR it's a NullReferenceException; on the Silverlight CLR it's the infamous Operation could destabilize the runtime.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

和影子一齐双人舞 2024-09-01 15:14:27

这是一个已修复的编译器错误。该代码从一开始就不应该是合法的,如果我们要允许它,我们至少应该生成有效的代码。我的不好。对于给您带来的不便,我们深表歉意。

It was a compiler bug that has been fixed. The code should never have been legal in the first place, and if we were going to allow it, we should have at least generated valid code. My bad. Sorry about the inconvenience.

抽个烟儿 2024-09-01 15:14:27

它不会导致编译时错误,因为它是一个有效的闭包。

问题是在创建闭包时 this 尚未初始化。当提供该参数时,您的构造函数尚未实际运行。因此,产生的 NullReferenceException 实际上是非常合乎逻辑的。 this 就是 null

我会向你证明这一点。让我们这样重写代码:

class Program
{
    static void Main(string[] args)
    {
        var test = new DerivedTest();
        object o = test.Func();
        Console.WriteLine(o == null);
        Console.ReadLine();
    }
}

class BaseTest
{
    public BaseTest(Func<object> func)
    {
        this.Func = func;
    }

    public Func<object> Func { get; private set; }
}

class DerivedTest : BaseTest
{
    public DerivedTest() : base(() => this)
    {
    }
}

猜猜这会打印什么?是的,它是true,闭包返回null,因为this在执行时没有初始化。

编辑

我对 Thomas 的声明很好奇,认为他们可能在后续的 VS 版本中改变了行为。我实际上发现了 Microsoft Connect 问题 关于这件事。它被关闭为“无法修复”。奇怪的。

正如微软在回应中所说,在基本构造函数调用的参数列表中使用 this 引用通常是无效的;该引用在该时间点根本不存在,如果您尝试“裸”使用它,您实际上会收到编译时错误。因此,可以说它应该为闭包情况产生编译错误,但是 this 引用对编译器是隐藏的,编译器必须知道(至少在 VS 2008 中)在封闭物内寻找它,以防止人们这样做。事实并非如此,这就是为什么你最终会出现这种行为。

It's not going to result in a compile-time error because it is a valid closure.

The problem is that this is not initialized yet at the time the closure is created. Your constructor hasn't actually run yet when that argument is supplied. So the resulting NullReferenceException is actually quite logical. It's this that's null!

I'll prove it to you. Let's rewrite the code this way:

class Program
{
    static void Main(string[] args)
    {
        var test = new DerivedTest();
        object o = test.Func();
        Console.WriteLine(o == null);
        Console.ReadLine();
    }
}

class BaseTest
{
    public BaseTest(Func<object> func)
    {
        this.Func = func;
    }

    public Func<object> Func { get; private set; }
}

class DerivedTest : BaseTest
{
    public DerivedTest() : base(() => this)
    {
    }
}

Guess what this prints? Yep, it's true, the closure returns null because this is not initialized when it executes.

Edit

I was curious about Thomas's statement, thinking that maybe they'd changed the behaviour in a subsequent VS release. I actually found a Microsoft Connect issue about this very thing. It was closed as "won't fix." Odd.

As Microsoft says in their response, it is normally invalid to use the this reference from within the argument list of a base constructor call; the reference simply does not exist at that point in time and you will actually get a compile-time error if you try to use it "naked." So, arguably it should produce a compile error for the closure case, but the this reference is hidden from the compiler, which (at least in VS 2008) would have to know to look for it inside the closure in order to prevent people from doing this. It doesn't, which is why you end up with this behaviour.

陌若浮生 2024-09-01 15:14:27

这个怎么样:

using System;
using System.Linq.Expressions;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var square = new Square(4);
            Console.WriteLine(square.Calculate());
        }
    }

    class MathOp
    {
        protected MathOp(Expression<Func<int>> calc) { _calc = calc.Compile(); }
        public int Calculate() { return _calc(); }
        private Func<int> _calc;
    }

    class Square : MathOp
    {
        public Square(int operand)
            : base(() => _operand * _operand)
        {
            _operand = operand;
        }

        private int _operand;
    }
}

How about this:

using System;
using System.Linq.Expressions;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var square = new Square(4);
            Console.WriteLine(square.Calculate());
        }
    }

    class MathOp
    {
        protected MathOp(Expression<Func<int>> calc) { _calc = calc.Compile(); }
        public int Calculate() { return _calc(); }
        private Func<int> _calc;
    }

    class Square : MathOp
    {
        public Square(int operand)
            : base(() => _operand * _operand)
        {
            _operand = operand;
        }

        private int _operand;
    }
}
本王不退位尔等都是臣 2024-09-01 15:14:27

您是否尝试过使用 () =>操作数 * 操作数 代替?问题在于,不确定 _operand 在您调用基数时是否会被设置。是的,它试图在您的方法上创建一个闭包,并且不能保证这里的顺序。

由于您根本没有设置 _operand,我建议仅使用 () =>;操作数 * 操作数 代替。

Have you tried using () => operand * operand instead? The issue is that there's no certainty that _operand will be set by the time you call the base. Yes, it's trying to create a closure on your method, and there's no guarantee of the order of things here.

Since you're not setting _operand at all, I'd recommend just using () => operand * operand instead.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文