61

我有这个代码:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    public int Sum{ get { return lazyGetSum.Value; } }

}

给我这个错误:

字段初始值设定项不能引用非静态字段、方法或属性。

我认为通过lazy访问非静态成员是非常合理的,如何做到这一点?

* 编辑 *

接受的答案完美地解决了问题,但要查看问题的详细和深入 - 一如既往 - 您可以阅读Joh Skeet 的答案

4

6 回答 6

99

您可以将其移动到构造函数中:

private Lazy<int> lazyGetSum;
public MyClass()
{
   lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
}

有关问题原因的更多详细信息,请参阅下面的@JohnSkeet 答案。 通过 Lazy<T> 或任何 lambda 表达式访问非静态成员

于 2012-12-25T09:36:59.177 回答
63

这是您的问题的简化版本:

class Foo
{
    int x = 10;
    int y = this.x;
}

还有一个稍微不那么简化的:

class Foo
{
    int x = 10;
    Func<int> y = () => this.x;
}

this通常是隐含的,但为了清楚起见,我在这里明确表示。)

在第一种情况下,使用this非常明显。

在第二种情况下,它稍微不那么明显,因为它由于 lambda 表达式而被延迟。但是仍然不允许...因为编译器会尝试构建一个用作this目标的委托,如下所示:

class Foo
{
    int x = 10;
    Func<int> y = this.GeneratedMethod;

    private int GeneratedMethod()
    {
        return x;
    }
}

C# 5 规范的第 10.5.5.2 节禁止这样做:

实例字段的变量初始值设定项不能引用正在创建的实例。

最简单的解决方法是将初始化放在构造函数主体中,您可以在其中引用this. 所以在你的代码中:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum;

    public MyClass()
    {
        lazyGetSum = new Lazy<int>(() => X + Y);
    }

    public int Sum{ get { return lazyGetSum.Value; } }
}

请注意,我也简化了 lambda 表达式——它很少值得使用new Func<int>(...)

于 2012-12-25T09:39:27.553 回答
3

该错误准确地告诉您出了什么问题。您无法访问字段初始值设定项中的属性。

假设你的类是这样的:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => 2 + 3));
    public int Sum { get { return lazyGetSum.Value; } }

}

然后它将毫无问题地编译。因为在您的代码中,您正在字段初始化中访问属性 X 和 Y。你得到了错误。

如果需要,您还可以在构造函数中初始化它们:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum; 
    public int Sum { get { return lazyGetSum.Value; } }

    public MyClass()
    {
        lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    }

}
于 2012-12-25T09:36:34.580 回答
0

我认为您需要定义static字段才能像这样使用它;

public  class MyClass
    {
        public static int X { get; set; }
        public static int Y { get; set; }

        private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
        public int Sum { get { return lazyGetSum.Value; } }
    }

当然,您需要初始化字段。你不能用其他方式做到这一点。

编辑:或者您可以在没有任何定义的情况下使用您的构造函数进行定义static

public MyClass()
    {
        lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    }

private Lazy<int> lazyGetSum; 
public int Sum { get { return lazyGetSum.Value; } }
于 2012-12-25T09:36:28.643 回答
0

如果要使用非静态方法。您还可以为 Lazy self 使用构建替代方案,该替代方案在 .Value 中获取 Func 参数,而不是在 Lazy 等构造函数中。

您的代码将如下所示:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private readonly LazyValue<int> lazyGetSum = new LazyValue<int>();
    public int Sum { get { return lazyGetSum.GetValue(() => X + Y); } }
}

它在属性中实现属性。就在你预期的地方,

于 2014-03-22T19:04:40.687 回答
0

当我遇到类似问题时发现了这个问题,并想找到一个好的模式来使用。

其他答案中建议的“将初始化移动到构造函数”的问题是初始化函数的 lambda 现在出现在构造函数中(实际上,在以前不需要的类中现在需要显式构造函数)。

这个玩具示例很好,但是在一个具有许多属性的更复杂的类中,将与属性相关的所有逻辑放在一个地方很有用。

亚历克斯·西普曼的回答提出了一个很好的选择,但是托管该LazyValue<>课程的第三方网站现在似乎是“服务不可用”,无论如何我都不是在寻找第三方解决方案,只是一种与正常情况一起使用的有效模式Lazy<>班级。我还担心在某些用例中,每次访问属性时创建委托实例和关联的闭包可能并非易事。

由于其他答案中突出显示的问题,在构造函数中有一行似乎是不可避免的,但我似乎最好避免将属性逻辑放在构造函数本身中,因为它将与任何差异等中的属性分开。

以下两种模式似乎有效,但我不确定是否有更好的选择我错过了。

模式 1:不要费心使用 lambda - 在属性旁边声明一个真实函数,并将其包装在隐式委托中:

public class MyClass
{
  public MyClass()
  {
    lazyGetSum = new Lazy<int>(GetSum);
  }

  public int X { get; set; }
  public int Y { get; set; }

  private Lazy<int> lazyGetSum;
  public int Sum { get { return lazyGetSum.Value; } }
  private int GetSum() { return X + Y; }
}

模式 2:声明一个属性 init 函数并从构造函数中调用它。

public class MyClass
{
  public MyClass()
  {
    LazyGetSumInit();
  }

  public int X { get; set; }
  public int Y { get; set; }

  private Lazy<int> lazyGetSum;
  public int Sum { get { return lazyGetSum.Value; } }
  private void LazyGetSumInit() { lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y)); }
}

看到两者并排,我想我更喜欢第二个,除了函数的名字看似笨拙。

[在我的实际实现中,我有一个类似于 的名称InitSum,因此它是“惰性”属性的实现细节,原则上可以在惰性和非惰性实现之间进行更改,而无需更改构造函数代码]

于 2018-03-19T10:58:17.433 回答