C# - 关闭初始化程序中的类字段?

时间:2010-03-15 23:59:57

标签: c# constructor lambda initialization closures

请考虑以下代码:

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上,臭名昭着的操作可能会破坏运行时的稳定性。

4 个答案:

答案 0 :(得分:14)

这是一个已修复的编译器错误。代码本来就不应该是合法的,如果我们要允许它,我们至少应该生成有效的代码。我的错。很抱歉给您带来不便。

答案 1 :(得分:11)

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

问题是在创建闭包时尚未初始化this。当提供该参数时,您的构造函数实际上还没有运行。因此,生成的NullReferenceException实际上非常符合逻辑。这是this的{​​{1}}!

我会向你证明。让我们用这种方式重写代码:

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在执行时没有被初始化。

修改

我很好奇托马斯的陈述,认为他们可能会在随后的VS版本中改变行为。我实际上发现了Microsoft Connect issue关于这件事。它被关闭为“不会修复”。奇

正如微软在回复中所说,在基础构造函数调用的参数列表中使用this引用通常是无效的;在那个时间点,引用根本就不存在,如果你试图“裸露”,你实际上会得到一个编译时错误。所以,可以说应该为闭包案例产生编译错误,但this引用对编译器是隐藏的,至少在VS 2008中,它必须知道在封闭内部查看以防止人们这样做。它没有,这就是为什么你最终会遇到这种行为。

答案 2 :(得分:2)

这个怎么样:

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;
    }
}

答案 3 :(得分:0)

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

由于您根本没有设置_operand,我建议您只使用() => operand * operand