3

I have a method that accepts a struct that contains many fields with basic datatypes.

I want to pass mostly defaults but with a couple tweaks, but I understand basic fields in a struct declaration can't contain default value declarations, e.g. struct S{ int a = 42 }.

Right now it goes like this:

OptionsStruct options = OptionStructDefaults;
options.itemA = 2;
options.itemQ = 2; // ... itemB, itemR, itemAZ12, etc.

SetOptions(options);

However, it feels weird to have OptionsStructDefaults information separate from the OptionsStruct declaration...

It feels like there'd be a way to do something like this?

SetOptions( new OptionsStruct(){
    itemA = 2,
    itemQ = 2,
    // ...all other fields have default values.
}) 

Or:

SetOptions( new OptionsStruct(useDefaults:true){
    itemA = 2,
    itemQ = 2,
});

Alternatively, is there a reason why separately specified struct defaults is ultimately the way to do things?

3
  • 2
    Could you use a constructor in the struct that sets most defaults but accepts a few optional params? Commented Mar 20, 2020 at 14:40
  • struct OptionsStruct { public static OptionStruct Default(int itemA = 2, int itemB = 2) { return new ... } } and use it like: SetOptions(OptionStruct.Default(itemB: 4)) Commented Mar 20, 2020 at 14:42
  • Sorry, in using item Q in the example I meant to demonstrate that I'd like to tweak -any- field, not just a couple specific ones. Will clarify in question. Commented Mar 20, 2020 at 15:52

1 Answer 1

11

Update: In C# 10+ (.NET 6+) this limitation is lifted. You now can have default constructors on structs, and you also can initialize fields and properties in place. Write your struct as if it was a class, and it will work:

public struct OptionsStruct
{
    public int ItemA { get; set; } = 2;
    public int ItemQ { get; set; } = 2;
}
    SetOptions(new OptionsStruct()
    {
        ItemA = 3,
        // ...all other fields have default values.
    });

However, there are 2 gotchas to be aware of:

  • If you have no constructors or a parameter-less constructor on your struct, the field and property initializations will work fine. But, they won't work if you have a constructor with parameters but no parameter-less constructors. If you have a with-parameter constructor, also write a parameter-less constructor (even if it's empty), so when you write new MyStruct(), the fields and properties are initialized as you expect. (This behavior is there to keep backward compatibility with the previous behavior of not calling a constructor when all parameters are optional. See the last code block on this answer for an example.)

    public struct NoConstructor
    {
        public int Item { get; set; } = 42;
    }
    
    public struct ParameterlessConstructor
    {
        public int Item { get; set; } = 42;
    
        public ParameterlessConstructor() { }
    }
    
    public struct WithParameterConstructor
    {
        public int Item { get; set; } = 42;
    
        public WithParameterConstructor(int item)
        {
            Item = item;
        }
    }
    
    public struct BothConstructors
    {
        public int Item { get; set; } = 42;
    
        public BothConstructors() { }
    
        public BothConstructors(int item)
        {
            Item = item;
        }
    }
    
        Console.WriteLine(new NoConstructor().Item); // 42
        Console.WriteLine(new ParameterlessConstructor().Item); // 42
        Console.WriteLine(new WithParameterConstructor().Item); // 0
        Console.WriteLine(new BothConstructors().Item); // 42
    
  • The parameter-less constructor is not guaranteed to be always called, so don't rely on your fields and properties being always initialized. If you create an array of a struct, like new MyStruct[5], or if you use default or default(MyStruct), no constructor will be called and the field and properties will be initialized to default values (0 or false or null), just like how it would be before C# 10.

    public struct MyStruct
    {
        public int FortyTwo { get; } = 42;
    
        public int GetFortyThree()
        {
            return FortyTwo + 1;
        }
    }
    
    public class SomeClass
    {
        MyStruct myStruct; // not initialized, same as default(MyStruct)
    
        public int GetFortyThree()
        {
            return myStruct.GetFortyThree();
        }
    }
    
        Console.WriteLine(new MyStruct().GetFortyThree()); // 43
        Console.WriteLine(default(MyStruct).GetFortyThree()); // 1
        var array = new MyStruct[5];
        Console.WriteLine(array[0].GetFortyThree()); // 1
        Console.WriteLine(new SomeClass().GetFortyThree()); // 1
    

Here's the link to the docs on the new C# 10 / .NET 6 feature: Parameterless constructors and field initializers

For C# 9 and below, read on...


The limitation on structs is that you cannot have a default constructor. So you cannot set default values to the fields and properties (because that is compiled into the default constructor).

But you can have a non-default constructor, i.e. a constructor that takes parameters.

So you can have a struct like this:

public struct OptionsStruct
{
    public int ItemA { get; set; }
    public int ItemQ { get; set; }

    public OptionsStruct(bool useDefaults)
    {
        if (useDefaults)
        {
            ItemA = 2;
            ItemQ = 2;
        }
        else
        {
            ItemA = 0;
            ItemQ = 0;
        }
    }
}

And use it as you wrote:

    SetOptions(new OptionsStruct(useDefaults:true)
    {
        ItemA = 3,
        // ...all other fields have default values.
    });

Another way to go is to have a static method that sets the default values:

public struct OptionsStruct
{
    public int ItemA { get; set; }
    public int ItemQ { get; set; }

    public static OptionsStruct GetDefault()
    {
        return new OptionsStruct()
        {
            ItemA = 2;
            ItemQ = 2;
        };
    }
}

And use it like this:

    var options = OptionsStruct.GetDefault();
    options.ItemA = 3;
    SetOptions(options);

If the number of your properties is not too much, you can also use a constructor with optional parameters:

public struct OptionsStruct
{
    public int ItemA { get; set; }
    public int ItemQ { get; set; }

    public OptionsStruct(int itemA = 2, int itemQ = 2)
    {
        ItemA = itemA;
        ItemQ = itemQ;
    }
}

But it will only be called if you give it at least one parameter:

    var options1 = new OptionsStruct(); // The "default" constructor is called, so everythng will be 0.
    Console.WriteLine(options1.ItemA); // 0
    Console.WriteLine(options1.ItemQ); // 0

    var options2 = new OptionsStruct(3); // Everything works as expected.
    Console.WriteLine(options1.ItemA); // 3
    Console.WriteLine(options1.ItemQ); // 2
Sign up to request clarification or add additional context in comments.

5 Comments

Ah, I had tried Options(bool defaults=true){...} without luck and thought having to be explicit about it all the time ( new Options(true)) seemed fishy. Constructor with optional params allowing new Options(itemQ:42); feels pretty good! Indeed it seems like it could be a little clunky with lots of options but could be worse. And I didn't realize you could have static methods on a struct! That'd be really handy for supporting different pre-made option sets!
In my opinion, it seems it has become too easy to add features to C# now that they have everything as managed code, as this new C# 10 feature will add more pitfalls for developers and will rely on tooling to help developers. I assume this should be paired with code analyzers or plugins for Visual Studio / Rider to highlight places where you're using a struct with default values where they will not be applied, and in my opinion this is the wrong direction to go.
@LasseV.Karlsen When you have a value-type structure in a language, there is no better way of adding a parameter-less constructor but this. Other solutions (like what C++ does) have bad performance implications. And not running the constructor when it is not explicitly called is very understandable (and is probably less confusing than the last code block on my answer). Overall, I think this is a very useful and very sensible feature to add to C#. (Well, except for the strange behavior when you have a constructor, but no parameter-less ones.)
... and the behavior when you use it in an array, or as a field in a class, or in any of the other cases where your fields will not be initialized. I will keep my opinion, this is not a good feature in my mind, it adds a minefield of cases where the new stuff will not be executed, and it's not at all clear from your code whether it will be or won't be.
@LasseV.Karlsen It is clear from the code whether it is executed. If you write new MyStruct() explicitly, it will be executed. If you don't, it won't be executed. You can see explicitly in your code that you are calling the constructor or not.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.