使用函数调用时,C# 6.0 默认 属性 值如何分配并受作用域影响?

How are C# 6.0 default property values assigned and affected by scope when using function calls?

我可能会在这里得到一些反对票,但我实际上通过正常搜索发现了 冲突 信息,并且希望其他人也可以轻松找到明确的答案。

在当前 C# 中给定一个 属性:

public static IEnumerable<string> foo { get; set; } = new string[] { "bar", "bar2" };

我们知道foo的默认值会return如上数组。如果分配了另一个值,则不再使用默认值。但是,如果情况是:

public static IEnumerable<string> foo { get; set; } = GetMyStrings();

我的想法是这个函数像:

if(foo == null) { foo = GetMyStrings();}

并且 foo 将在对象的生命周期内保留其 GetMyStrings() 的第一个 运行 的值,除非被手动分配的值覆盖。

我的同事坚持认为这存在 GC 问题的风险,GetMyStrings() 的结果可能超出范围并被收集,"renullifying" 参数并导致多次调用 GetMyStrings()在对象的生命周期内。

我们谁是正确的?

不对,其实是这样的:

static ClassName()
{
    foo = GetMyStrings();
}

编译器生成一个静态构造函数并将赋值调用放在那里(就像它在构造函数中为非静态属性创建一行一样)。

My colleague insists this risks a GC problem, and that the result of GetMyStrings() can fall out of scope and be collected, "renullifying" the parameter and causing multiple calls to GetMyStrings() over the lifetime of the object.

他在胡说八道。分配的实例永远不会被垃圾回收,无论它是从哪里分配的。

写这个

public static IEnumerable<string> Foo { get; set; } = GetMyStrings();

是这个声明的语法糖

public static IEnumerable<string> Foo { get; set; }

并添加了如下所示的静态构造函数:

static MyClass() {
    Foo = GetMyStrings();
}

(如果您已经有一个静态构造函数,Foo = GetMyStrings(); 行会隐式添加到它)。

除此之外,没有区别:once it is time for the class to get initializedGetMyStrings()是从构造函数中调用的,然后returns的值赋给了Foo。之后该值将保留在那里,直到被另一个值替换或程序结束。

就 "renullifying" 参数而言,它永远不会发生在 class 对象的同一实例上。当包含 class 的应用程序域被卸载时,或者当 class 被加载到另一个应用程序域时,您可以多次调用 GetMyStrings() ,但这与新的 C# 6 无关语法。

最后,如果您不打算在静态构造函数之后更改Foo,请考虑将其设置为只读:

public static IEnumerable<string> Foo { get; } = GetMyStrings();

为了举例说明我的评论,请看这个例子:

public void RunTest()
{
    Test t = new Whosebug.Form1.Test();
    Console.WriteLine(t.Values.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values.First());

    Console.WriteLine("------");
    Console.WriteLine(t.Values2.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values2.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values2.First());

    Console.WriteLine("------");
    Console.WriteLine(t.Values3.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values3.First());
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine(t.Values3.First());
}

public class Test
{
    public IEnumerable<string> Values { get; set; } = GetValues();

    public static IEnumerable<string> GetValues()
    {
        List<string> results = new List<string>();
        for (int i = 0; i < 10; ++i)
        {
            yield return DateTime.UtcNow.AddMinutes(i).ToString();
        }
    }

    public IEnumerable<string> Values2 { get; set; } = GetValues2();

    public static IEnumerable<string> GetValues2()
    {
        return GetValues().ToList();
    }

    public IEnumerable<string> Values3 { get; set; } = GetValues().ToList();
}

这个输出是:

19/04/2017 12:24:25
19/04/2017 12:24:26
19/04/2017 12:24:27
------
19/04/2017 12:24:25
19/04/2017 12:24:25
19/04/2017 12:24:25
------
19/04/2017 12:24:25
19/04/2017 12:24:25
19/04/2017 12:24:25

值:如您所见,使用 yield 的方法中的 IEnumerable<> 将在您每次调用它时进行计算。

Values2:方法 return 是一个列表。由于 List 实现了 IEmnumerable,您仍然只是 return List 的具体实例(尽管作为其 IEnumerable 接口呈现),因此它只被评估一次。

Values3:我们基本上将 .ToList() 移到了 GetValues2() 方法之外,直接在 属性 上实现了它。此处的结果与Values2相同,原因基本相同。

当您 return 一个实现 IEnumerable 的具体对象时,它将被计算一次,但要注意第一个示例。

请记住,Linq 是一个视图。如果基础数据发生变化,return从您的 Linq 表达式编辑的内容也会发生变化。


关于垃圾回收问题:活动对象引用的对象不会被垃圾回收。