如何为C#自动属性赋初值?

2320

如何给 C# 自动属性赋初始值?

我通常使用构造函数,或者回归旧的语法。

使用构造函数:

class Person 
{
    public Person()
    {
        Name = "Initial Name";
    }
    public string Name { get; set; }
}

使用常规的属性语法(带有初始值)

private string name = "Initial Name";
public string Name 
{
    get 
    {
        return name;
    }
    set
    {
        name = value;
    }
}

有更好的方法吗?

23个回答

2937

在C# 5及更早版本中,要给自动实现的属性设置初始值,必须在构造函数中完成。

自从C# 6.0以后,您可以使用内联语法指定初始值。语法如下:

public int X { get; set; } = x; // C# 6 or higher

DefaultValueAttribute 旨在供 VS 设计器(或其他使用者)指定默认值,而不是初始值(即使在设计对象中,初始值是默认值)。

编译时,DefaultValueAttribute 不会影响生成的 IL 并且不会读取以将属性初始化为该值(请参见 DefaultValue attribute is not working with my Auto Property)。

影响 IL 的属性示例包括 ThreadStaticAttributeCallerMemberNameAttribute 等。


12
值得注意的是,此方法也适用于只具有 getter 方法的属性:public int X { get; } = x; - Olivier Jacot-Descombes
1
只有 setter 怎么办? - ATL_DEV
1
@Narish:你的版本只适用于x是一个常量、字面值或只读字段的情况。否则,它将在每次调用时返回当前可能会变化的x的值。而在我的版本中,x可以是一个静态可读写的字段,并且它将始终返回相同的值,即使字段发生变化。 - Olivier Jacot-Descombes
1
@ATL_DEV,一个只读属性不能是自动属性。 - Olivier Jacot-Descombes
1
请参见:https://sharplab.io/#gist:a8cf845d585e24e4d4d1a5021ed863ad。右侧面板显示了反编译的IL代码。这就是C#的真实运行方式。 - Olivier Jacot-Descombes
显示剩余12条评论

330
C# 6:
使用C# 6,你可以直接初始化自动属性(终于!),现在有其他答案描述了这一点。
C# 5及以下:
虽然属性的预期用途并不是实际设置属性的值,但你仍然可以使用反射来始终设置它们...
public class DefaultValuesTest
{    
    public DefaultValuesTest()
    {               
        foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(this))
        {
            DefaultValueAttribute myAttribute = (DefaultValueAttribute)property.Attributes[typeof(DefaultValueAttribute)];

            if (myAttribute != null)
            {
                property.SetValue(this, myAttribute.Value);
            }
        }
    }

    public void DoTest()
    {
        var db = DefaultValueBool;
        var ds = DefaultValueString;
        var di = DefaultValueInt;
    }


    [System.ComponentModel.DefaultValue(true)]
    public bool DefaultValueBool { get; set; }

    [System.ComponentModel.DefaultValue("Good")]
    public string DefaultValueString { get; set; }

    [System.ComponentModel.DefaultValue(27)]
    public int DefaultValueInt { get; set; }
}

6
投票-1:最好情况下,它看起来比在构造函数中初始化要整洁一些。但这是以让新开发者困惑、性能更差、语义上改变内置属性的意义、只允许常量、难以在多个属性中找到默认值、需要记住在每个构造函数重载中运行此操作,并且可能在属性和构造函数中都定义默认值的代价为代价的。 - Jason
我更倾向于使用普通属性(而不是自动属性),在私有变量上设置默认值,或在构造函数中初始化属性,而不是转向反射。 - Robert Harvey

213

当您为变量内联初始值时,它将在构造函数中隐式完成。

我认为在C#中,这种语法到5版本为止都是最佳实践:

class Person 
{
    public Person()
    {
        //do anything before variable assignment

        //assign initial values
        Name = "Default Name";

        //do anything after variable assignment
    }
    public string Name { get; set; }
}

这使您可以清晰地控制分配值的顺序。

从C#6开始,有一种新方法:

public string Name { get; set; } = "Default Name";

76

有时候,如果我不想将它真正设置并存储在我的数据库中,我会使用这个:

class Person
{
    private string _name; 
    public string Name 
    { 
        get 
        {
            return string.IsNullOrEmpty(_name) ? "Default Name" : _name;
        } 

        set { _name = value; } 
    }
}

显然,如果它不是一个字符串,那么我可能会使这个对象可为空(double?, int?),并检查它是否为空,返回一个默认值,或者返回它被设置的值。

然后在我的仓库中进行检查,看看它是否是我的默认值并且不持久化,或者进行后门检查以查看后备值的真实状态,然后再保存。


38
return _name ?? "默认名称"; 这样甚至更清晰,你的。 - abatishchev
23
尽管不完全相同,但 Crucible 的代码会在字符串为 "" 或 null 时返回 "Default Name",而使用您的方法仅在字符串为 null 时返回 "Default Name"。另外,是否使用 "??" 或 "IsNullOrEmpty" 更清晰还有待讨论。 - Sebastian Mach
2
点是一个默认值,因此可空检查会失去意义。Keith的答案通过在Ctor中初始化来证明这一点。如果是为了数据库,我真的看不出与具有默认列值并使其成为非空列的区别,无论类字段数量如何,都将更有效率。我不会投反对票,但敦促开发人员考虑这一点,而不是在属性过程中进行空/空检查。 - Jeremy Thompson
2
为了澄清,每次调用类属性时都会进行空值/空检查,而数据库仅在INSERT或UPDATE时进行检查,这通常占据数据库工作的20%。相反,每个字符串属性可能都有一个额外的调用,这是CPU周期的浪费和不良设计选择,在我看来。此外,现在有Null Ref Type,因此处理可为空的情况更加普遍。 - Jeremy Thompson

72

在C# 6.0中,这是小菜一碟!

您可以在类声明本身中,在属性声明语句中执行此操作。

public class Coordinate
{ 
    public int X { get; set; } = 34; // get or set auto-property with initializer

    public int Y { get; } = 89;      // read-only auto-property with initializer

    public int Z { get; }            // read-only auto-property with no initializer
                                     // so it has to be initialized from constructor    

    public Coordinate()              // .ctor()
    {
        Z = 42;
    }
}

3
我还没有 C#6.0,正在查询默认自动属性的所需版本。C#6.0 是否也不需要使用 { get; set; }{ get; private set; },否则编译器将阻止设置值? - freefaller

45

38
C# (6.0)及以上版本中,您可以使用以下方式:

对于只读属性

public int ReadOnlyProp => 2;

对于可写和可读属性

public string PropTest { get; set; } = "test";

在当前版本的C# (7.0)中,您可以使用表达式主体的get/set访问器来使其与支持字段一起使用时更加简洁。(以下是演示如何使用该功能的代码段)

private string label = "Default Value";

// Expression-bodied get / set accessors.
public string Label
{
   get => label;
   set => this.label = value; 
 }

8
此外,考虑以下示例 class C { public DateTime P { get; } = DateTime.Now; public DateTime Q => DateTime.Now; },其中属性 PQ 都只有 getter,但是它们的行为非常不同! - Jeppe Stig Nielsen

36
在C# 9.0中,添加了对init关键字的支持 - 这是一种非常有用且极其复杂的方式,用于声明只读自动属性
声明:
class Person 
{ 
    public string Name { get; init; } = "Anonymous user";
}

~享受~ 使用:

// 1. Person with default name
var anonymous = new Person();
Console.WriteLine($"Hello, {anonymous.Name}!");
// > Hello, Anonymous user!


// 2. Person with assigned value
var me = new Person { Name = "@codez0mb1e"};
Console.WriteLine($"Hello, {me.Name}!");
// > Hello, @codez0mb1e!


// 3. Attempt to re-assignment Name
me.Name = "My fake"; 
// > Compilation error: Init-only property can only be assigned in an object initializer

7
以前我们只能使用public string Name { get; } = "Anonymous user";,版本9的实现更有用,可以增加设置值的范围。这个答案暗示我们必须等待C# 9,但对于OP期望的行为来说,这是不正确的。 - Chris Schaller

23

除了已被接受的答案之外,如果您想将默认属性定义为其他属性的函数,您可以在C#6.0(及更高版本)上使用表达式主体符号来创建更加优雅和简洁的结构,例如:

public class Person{

    public string FullName  => $"{First} {Last}"; // expression body notation

    public string First { get; set; } = "First";
    public string Last { get; set; } = "Last";
}
您可以按照以下方式使用上述内容。
    var p = new Person();

    p.FullName; // First Last

    p.First = "Jon";
    p.Last = "Snow";

    p.FullName; // Jon Snow

为了能够使用上述的"=> "符号,属性必须是只读的,且不能使用get访问器关键字。

有关详细信息,请参阅MSDN


FullName是一个没有后备字段的计算属性,因此这并不能真正回答问题,问题是关于自动属性的。 - Lance U. Matthews

15
在 C# 6 及以上版本中,您可以简单地使用以下语法:

public object Foo { get; set; } = bar;
请注意,要有一个readonly属性,只需省略set即可:
public object Foo { get; } = bar;

您也可以从构造函数中为readonly自动属性分配值。

在此之前,我的回答如下。

我建议避免在构造函数中添加默认值;让它留给动态赋值,并避免变量在类型默认和构造函数中被赋值(即在两个地方)。在这种情况下,通常我会简单地编写普通属性。

另一个选项是像ASP.Net一样通过属性定义默认值:

http://msdn.microsoft.com/en-us/library/system.componentmodel.defaultvalueattribute.aspx


1
哇,这真是一次时光之旅。我记得这是基于规范的阅读(部分摘录在此:https://msdn.microsoft.com/en-us/library/aa645756(v=vs.71).aspx)。考虑到时间和版本数量(以及Roslyn),现在可能已经不是这种情况了。尽管我们希望能够提供一个对照参考。 - Lex
1
默认赋值会自动发生,无论您是否使用初始值或在构造函数中分配。存在轻微的语义差异 - 字段分配发生在构造函数调用之前 - 但空值赋值仍将发生。请参见10.4.5“所有实例字段...首先初始化为它们的默认值,然后执行实例字段初始化程序”https://msdn.microsoft.com/en-us/library/aa645757(VS.71).aspx - Mark Brackett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接