题 如何为C#Auto-Property提供默认值?


如何为C#Auto-Property提供默认值?我要么使用构造函数,要么还原为旧语法。

 使用构造函数:

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

使用普通属性语法  (使用默认值)

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

有没有更好的办法?


1424
2017-09-02 21:29


起源


它具有误导性,它甚至在IntelliSense中说“设置属性的默认值”,但在MSDN上它说,“DefaultValueAttribute不会导致成员使用属性的值自动初始化。您必须设置初始值你的代码。“,Urg! - Chris
这是vb.net具有明显优势的少数几个地方之一,他们可以在定义中为属性设置默认值。 - Blindy
为什么你不想用初始化字段来支持该属性?这就是编译器无论如何都实现自动属性的方式。显式字段也具有可以声明它的优点 readonly。 - finnw
@finnw IMO是关于提高代码可读性的。 - Keith
这是我唯一喜欢VB.NET而非C#的东西。 Public Property MyName As String = "Default Name" - Alex Yeung


答案:


在C#5和更早版本中,要为自动实现的属性提供默认值,您必须在构造函数中执行此操作。

自C#6.0以来,包含了自动属性初始值设定项的功能。语法是:

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

1580
2017-09-02 21:46



你错了。抽象类可以有构造函数。 - Darren Kopp
实际上,抽象类通常具有受保护的构造函数。 - Ludovic Chabant
抽象类通常具有您给它们的任何可访问性。如果您未指定构造函数,则始终具有默认的公共构造函数 - Darren Kopp
抽象类的构造函数永远不应该被赋予公共访问权限,因为它们永远不能被实例化。这样做会导致FxCop违规(希望您使用它)并违反.NET Framework设计指南 - amazon.com/Framework-Design-Guidelines-Conventions-Libraries/dp/... - Dave Black
@HankSchultz不,对象初始化器只是编译器所做的糖。实际发出的是: var person = new Person(); persion.Name = "My Value"; 构造函数始终先运行 - Darren Kopp


编辑1/2/15

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

242
2018-06-22 18:14



是的,这就是答案:[System.ComponentModel.DefaultValue(GiveAnyTypeOfDefaultValueHere)] - WAP Guy
+1的聪明才智,但这可能真的很慢。想像 new在一个内循环中的一堆项目,而没有意识到构造函数使用反射... - Patrick M
如果基类使用静态构造函数将属性和值加载到字典中,则每个类都没有开销! - Gayot Fow
你推荐的确很糟糕。它很慢,并且引入了非常简单的类的反射。如果它不是那么慢,可以使用某种类型的il-weaving具有这样的属性(PostSharp,Fody等),但性能...... - Grigory
只对VS设计师有利,甚至不是在所有情况下(即asp.net),无论如何..这在运行时不会做任何事情。 - G.Y


当你为变量内联一个初始值时,无论如何都会在构造函数中隐式地完成它。

我认为这种语法是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"

129
2017-09-04 12:16



如果构造函数需要以不同的方式(跨多个构造函数)进行初始化,那么这只是最佳实践。如果他们都以相同的方式初始化,那么如果我还想要Person(字符串名称),那么你不是建议不好的做法,然后我添加属性int ID而我还有另一个构造函数Person(字符串名,int id)?违反开放延期,关闭修改。我认为,只要所有构造函数以相同的方式初始化成员,最佳实践就是在行初始化。 - dbobrowski
@dbobrowski如果我有多个构造函数我会使用 public Person(string name, int id) : this() 语法,但你有一个观点。这使您可以更精确地控制何时以内联选项更简单的可维护性为代价初始化属性。 - Keith
@Keith我更喜欢 public Person():this("Default Name")而不是设置名称两次。 - ANeves
如果你需要在几个地方运行相同的代码,你就不会诉诸于没有人听说过的反射和奇怪的属性。你提取一个辅助方法,或者在真正复杂的地方,创建一个工厂(或者只是重新设计/分解你的类,因为它可能太复杂了) - kai


DefaultValueAttribute仅适用于vs设计器。它不会将属性初始化为该值。

看到 DefaultValue属性不适用于我的自动属性


73
2017-09-02 22:44



+1:[DefaultValue]用于告诉设计默认值是什么。如果在属性窗口中将值设置为此默认值,则不会将值写入生成的代码。同样,非默认值在属性窗口中以粗体显示。当然,这仅在[DefaultValue]实际具有正确值时有效。它没有什么可以强制执行的。 - Roger Lipscombe
DefaultValueAttribute 不只是为了设计师。但是,“默认值”与“初始值”不同。 - Ben Voigt


有时我会使用它,如果我不希望它实际设置并持久保存在我的数据库中:

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

        set { _name = value; } 
    }
}

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

然后,我可以在我的存储库中检查它是否是我的默认值而不是持久性,或者在保存之前检查后门是否检查支持值的真实状态。

希望有所帮助!


48
2017-09-02 23:07



return _name ?? "Default Name"; 甚至可能更清楚你的 - abatishchev
@abatishchev:虽然那不一样。如果字符串为“”或null,则crucibles代码将返回“Default Name”,但是只有在它为null时,使用您的方法才会返回“Default Name”。另外,可以讨论是否“??”或者“IsNullOrEmpty”更清楚。 - Sebastian Mach
@phresnel:如果属性值可以是“”并且它与null相同那么你就是对的。否则“”是有意义的值,我的代码更好 - abatishchev
private string _name = "Default Name"; public string Name { get { return _name; } set { _name = value; } } - Gilbert
@Gilbert - 你重复了问题中显示的一个代码选项。 Crucible的答案专门用于“我不希望我的数据库中保留默认值”的情况。也就是说,他希望能够在其他地方确定_name是否已被写入,或者仍为空。 [他的写或持久的代码检查 _name 为null,跳过写它。] - ToolmakerSteve


从C#6.0开始,我们可以为自动实现的属性分配默认值。

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

我们还可以创建只读的自动实现属性,如:

public string Name { get; } = "Some Name";

看到: C#6:第一反应,自动实现属性的初始化程序 - 作者:Jon Skeet


24
2018-04-29 14:40





在C#6.0中,这是一件轻而易举的事!

你可以在 Class 声明本身,在财产声明中。

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

17
2017-07-25 19:35



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


小完整样本:

using System.ComponentModel;

private bool bShowGroup ;
[Description("Show the group table"), Category("Sea"),DefaultValue(true)]
public bool ShowGroup
{
    get { return bShowGroup; }
    set { bShowGroup = value; }
}

11
2018-06-17 07:44



那不行。 DefaultValueAttribute 只是一个序列化提示,它不会设置 ShowGroup 至 true 因为任何布尔值的默认值是 false。 - Boris B.


在版本中 C#(6.0)及以上, 你可以做 :

对于Readonly属性

public int ReadOnlyProp => 2;

对于Writable和Readable属性

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

11
2018-03-15 01:07



这很好,但只有三个例子中的第二个是自动属性。 - Aluan Haddad


我的解决方案是使用自定义属性,该属性通过常量或使用属性类型初始化程序提供默认值属性初始化。

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class InstanceAttribute : Attribute
{
    public bool IsConstructorCall { get; private set; }
    public object[] Values { get; private set; }
    public InstanceAttribute() : this(true) { }
    public InstanceAttribute(object value) : this(false, value) { }
    public InstanceAttribute(bool isConstructorCall, params object[] values)
    {
        IsConstructorCall = isConstructorCall;
        Values = values ?? new object[0];
    }
}

要使用此属性,必须从特殊的基类初始化程序继承一个类或使用静态帮助程序方法:

public abstract class DefaultValueInitializer
{
    protected DefaultValueInitializer()
    {
        InitializeDefaultValues(this);
    }

    public static void InitializeDefaultValues(object obj)
    {
        var props = from prop in obj.GetType().GetProperties()
                    let attrs = prop.GetCustomAttributes(typeof(InstanceAttribute), false)
                    where attrs.Any()
                    select new { Property = prop, Attr = ((InstanceAttribute)attrs.First()) };
        foreach (var pair in props)
        {
            object value = !pair.Attr.IsConstructorCall && pair.Attr.Values.Length > 0
                            ? pair.Attr.Values[0]
                            : Activator.CreateInstance(pair.Property.PropertyType, pair.Attr.Values);
            pair.Property.SetValue(obj, value, null);
        }
    }
}

用法示例:

public class Simple : DefaultValueInitializer
{
    [Instance("StringValue")]
    public string StringValue { get; set; }
    [Instance]
    public List<string> Items { get; set; }
    [Instance(true, 3,4)]
    public Point Point { get; set; }
}

public static void Main(string[] args)
{
    var obj = new Simple
        {
            Items = {"Item1"}
        };
    Console.WriteLine(obj.Items[0]);
    Console.WriteLine(obj.Point);
    Console.WriteLine(obj.StringValue);
}

输出:

Item1
(X=3,Y=4)
StringValue

9
2018-01-17 22:04



如上所述,使用反射来初始化默认值既缓慢又过度。在构造函数上初始化,使用非自动属性或在c#6及更高版本上,使用接受的答案中显示的简化表示法 - KinSlayerUY


除了已经接受的答案之外,对于您要将默认属性定义为的方案 功能 您可以使用的其他属性 表达体符号 在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 accessor关键字。

细节 MSDN


8
2017-10-30 02:16