如何在C#中为结构体设置默认值?

40

我试图为我的结构体设置默认值。例如,Int的默认值为0,DateTime的默认值为1/1/0001 12:00:00 AM。众所周知,在结构体中无法定义无参数构造函数。

struct Test
{
    int num;
    string str;
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(default(Test)); // shows namespace and name of struct test.Test
        Console.WriteLine(new Test()); // same

        Console.ReadKey(true);
    }
}

如何为结构体设置默认值?


阅读类似文章:https://dev59.com/X1PTa4cB1Zd3GeqPfgm6 - Ruslan Borovok
6个回答

54

你不能这样做。结构体总是被预先清零,也无法保证构造函数会被调用(例如:new MyStruct[10])。如果你需要除了零以外的默认值,你需要使用类。这就是为什么一开始你不能改变默认构造函数的原因(在C#6之前)——这个构造函数从未执行。

你可以通过使用Nullable字段来实现最接近的效果,并通过属性将它们解释为具有某些默认值(如果它们为空):

public struct MyStruct
{
  int? myInt;

  public int MyInt { get { return myInt ?? 42; } set { myInt = value; } }
}

myInt仍然是预先清零的,但你可以将“零”解释为你自己的默认值(在本例中为42)。当然,这可能是完全不必要的开销 :)

至于Console.WriteLine,它只调用虚拟的ToString方法。你可以将其更改为返回任何你想要的内容。


4
太聪明了!只要记住,即使MyInt返回42,两个MyStruct实例可能相等也可能不相等。 - Syndog
1
我之前遇到了同样的问题,但是我通过添加一个隐藏的布尔标记来解决它,该标记可以让我知道这是默认结构还是使用ctor创建的结构。它已经按照预期运作。 在 MyStrutctCtor(...)函数中添加 _isInitializedHiddenFlag = true;,然后,如果结构是默认的,我可以使用任何备选值。我还在比较方法中使用它来区分默认值和手动设置的等价值。 - StackHola
同意非常聪明。但是我觉得C#结构体不允许某种默认初始化很奇怪。即使在C++中也可以做到这一点(我知道,我知道,C++的结构体和类几乎是一样的)。但是CLR肯定没有技术原因不能允许这样做。看起来像是一个巨大的疏忽或某种过度的哲学决定。 - Emperor Eto
2
@PeterMoore,它们被清零了,没有比这更复杂的了。浮点数也会初始化为0,因为清零的内存在浮点数中就是一个零值 ;) - Luaan
不知为何我一直以为0x00000000不会映射到0f,但我肯定是在吸毒。那么,如果所有东西都被清零了,我想缺少可配置的默认值也就有些合理了! - Emperor Eto
显示剩余2条评论

5

您的问题与C#/.Net的行为无关。您实例化结构体的方式实际上创建了一个具有所有成员字段默认值的实例。

Console.WriteLine使用ToString()方法将其参数转换为字符串。默认实现(Object.ToString())只是写入完全限定的类名(命名空间和名称,正如您所称)。

如果您想要另一种可视化方式,您应该重写ToString方法:

public struct Test
{
    int num;
    string str;
    public override string ToString()
    {
        return $"num: {num} - str: {str}";
    } 
}

2
这是我的看法,如果有人觉得有用的话。
public struct MyStruct
{
    public int item1;
    public float item2;
    public float item3;

    public static MyStruct Null => new MyStruct(-1, 0, 0);
}

我在结构体内部定义了一个静态方法,以便可以这样操作:

var data = MyStruct.Null;

替代

var data = new MyStruct();
data.item1 = -1;
...

或者创建一个自定义构造函数来传递数据。

1

除非你覆盖 .ToString() 方法,否则使用命名空间打印 C# 结果的对象。你可以像下面这样定义你的结构体并尝试它吗?

public struct Test
{
    int num;
    string str;
    public override string ToString()
    {
        return "Some string representation of this struct";
    }
}

PS: default(Test)会返回一个结构体,其中包含default(int)和default(string),我指的是Test.num为0,Test.str为null

希望这可以帮到您


1
你也可以这样做:

public struct MyStruct
{
    public static readonly Default = new MyStruct(42);

    public int i;

    public MyStruct(int i)
    {
        this.i = i;
    }
}

当您创建此类型的默认结构时,请执行以下操作:
public MyStruct newStruct = MyStruct.Default;

当然,这不会覆盖默认值,其他程序员可能会碰几次壁。从微软文档中认真考虑结构体是否是正确的选择:
“结构类型(或结构体类型)是可以封装数据和相关功能的值类型。通常,您使用结构类型设计小型数据中心类型,这些类型提供很少或没有行为。”
请考虑这一点:如果您的结构体中有2个值,并且您想要创建构造函数,那么2个或更少的构造函数是否足够?如果答案是否定的,则答案是:不要使用结构体。

0

你可能想要做的是重写 ToString() 方法,例如:

struct Test
{
    int num;
    string str;

    public override string ToString ()
    {
        return string.Format ($"{str} | {num}");
    }
}

正如您所提到的,除了针对其适当类型的默认值外,无法为其他字段定义默认值。但是,通过重写 ToString() 方法,在控制台和调试过程中,您将看到有关结构的更好格式化信息。


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