문제

I'd like to enforce a struct to always be valid regarding a certain contract, enforced by the constructor. However the contract is violated by the default operator.

Consider the following, for example:

struct NonNullInteger
{
    private readonly int _value;

    public int Value
    {
        get { return _value; }
    }

    public NonNullInteger(int value)
    {
        if (value == 0)
        {
            throw new ArgumentOutOfRangeException("value");
        }

        _value = value;
    }
}

// Somewhere else:
var i = new NonNullInteger(0); // Will throw, contract respected
var j = default(NonNullInteger); // Will not throw, contract broken

As a workaround I changed my struct to a class so I can ensure the constructor is always called when initializing a new instance. But I wonder, is there absolutely no way to obtain the same behavior with a struct?

도움이 되었습니까?

해결책

One approach is to arrange things so that the default value satisfies the contract:

struct NonNullInteger
{
    private readonly int _valueMinusOne;

    public int Value
    {
        get { return _valueMinusOne + 1; }
    }

    public NonNullInteger(int value)
    {
        if (value == 0)
        {
            throw new ArgumentOutOfRangeException("value");
        }

        _valueMinusOne = value - 1;
    }
}

다른 팁

I don't see how you could do this, because, unlike a class, a struct always has a default parameterless constructor; given the way your struct is written, a value of 0 cannot be prevented:

Structs cannot contain explicit parameterless constructors. Struct members are automatically initialized to their default values.

An immutable class is preferable in this case, since the default state is invalid. It's a bit more costly as far as memory usage, but that shouldn't matter unless you using a very large number of these. Really though, a 'non-zero number' constraint is probably better handled at a contractual level for each method, rather than put into a class.

If you really want to enforce the contract, put the exception in the Value getter instead of the constructor. Then the contract is that you'll throw an exception if it ever did contain a 0 value; the only real benefit here is that you never silently use a zero value. The downside is that now you have a comparison every time you use the value.

While you can't achieve exactly what you want you could validate in the getter:

public int Value
{
    get 
    { 
        if (_value == 0) 
            throw new InvalidOperationException("Use of uninitialized struct"); 
        return _value; 
    }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top