tags:

views:

703

answers:

6

I'm looking for a way to allow a property in a C# object to be set once only. It's easy to write the code to do this, but I would rather use a standard mechanism if one exists.

public OneShot<int> SetOnceProperty { get; set; }

What I want to happen is that the property can be set if it is not already set, but throw an exception if it has been set before. It should function like a Nullable value where I can check to see if it has been set or not.

+15  A: 

There is direct support for this in the TPL in .NET 4.0; until then just do the check yourself... it isn't many lines, from what I recall...

something like:

public sealed class WriteOnce<T>
{
    private T value;
    private bool hasValue;
    public override string ToString()
    {
        return hasValue ? Convert.ToString(value) : "";
    }
    public T Value
    {
        get
        {
            if (!hasValue) throw new InvalidOperationException("Value not set");
            return value;
        }
        set
        {
            if (hasValue) throw new InvalidOperationException("Value already set");
            this.value = value;
            this.hasValue = true;
        }
    }
    public T ValueOrDefault { get { return value; } }

    public static implicit operator T(WriteOnce<T> value) { return value.Value; }
}

Then use, for example:

readonly WriteOnce<string> name = new WriteOnce<string>();
public WriteOnce<string> Name { get { return name; } }
Marc Gravell
I was so busy polishing my own answer that I didn't notice you wrote (practially) the same darned thing. I don't feel so special anymore.
Michael Meadows
+9  A: 

You can roll your own (see the end of the answer for a more robust implementation that is thread safe and supports default values).

public class SetOnce<T>
{
    private bool set;
    private T value;

    public T Value
    {
        get { return value; }
        set
        {
            if (set) throw new AlreadySetException(value);
            set = true;
            this.value = value;
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}

You can use it like so:

public class Foo
{
    private readonly SetOnce<int> toBeSetOnce = new SetOnce<int>();

    public int ToBeSetOnce
    {
        get { return toBeSetOnce; }
        set { toBeSetOnce.Value = value; }
    }
}

More robust implementation below

public class SetOnce<T>
{
    private readonly object syncLock = new object();
    private readonly bool throwIfNotSet;
    private readonly string valueName;
    private bool set;
    private T value;

    public SetOnce(string valueName)
    {
        this.valueName = valueName;
        throwIfGet = true;
    }

    public SetOnce(string valueName, T defaultValue)
    {
        this.valueName = valueName;
        value = defaultValue;
    }

    public T Value
    {
        get
        {
            lock (syncLock)
            {
                if (!set && throwIfNotSet) throw new ValueNotSetException(valueName);
                return value;
            }
        }
        set
        {
            lock (syncLock)
            {
                if (set) throw new AlreadySetException(valueName, value);
                set = true;
                this.value = value;
            }
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}


public class NamedValueException : InvalidOperationException
{
    private readonly string valueName;

    public NamedValueException(string valueName, string messageFormat)
        : base(string.Format(messageFormat, valueName))
    {
        this.valueName = valueName;
    }

    public string ValueName
    {
        get { return valueName; }
    }
}

public class AlreadySetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has already been set.";

    public AlreadySetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}

public class ValueNotSetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has not yet been set.";

    public ValueNotSetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}
Michael Meadows
Only you're not setting this.set to true when you set the value... ;)
Tomas Lycken
There we go now we are setting it to true
JoshBerke
Thanks. I saw that, but then started seeing other issues too. I put a more "production ready" version at the bottom of the answer.
Michael Meadows
+1 for the thread safety.
Marc Gravell
+1  A: 

No such feature in C# (as of 3.5). You have to code it yourself.

Vizu
Microsoft should provide such feature.
Syed Tayyab Ali
I'm not sure. If something should be set only once, then it probably should be a constructor parameter.
Vizu
+9  A: 

This can be done with either fiddling with flag:

private OneShot<int> setOnce;
private bool setOnceSet;

public OneShot<int> SetOnce
{
    get { return setOnce; }
    set
    {
        if(setOnceSet)
            throw new InvalidOperationException();

        setOnce = value;
        setOnceSet = true;
    }
}

which is not good since you can potentially receive a run-time error. It's much better to enforce this behavior at compile-time:

public class Foo
{
    private readonly OneShot<int> setOnce;        

    public OneShot<int> SetOnce
    {
        get { return setOnce; }
    }

    public Foo() :
        this(null)
    {
    }

    public Foo(OneShot<int> setOnce)
    {
        this.setOnce = setOnce;
    }

}

and then use either constructor.

Anton Gogolev
I want there to be a run time error if I try to set it twice. Otherwise I would have created a constructor that sets a readonly member variable
Nick R
+1 for setting the value in the constructor
Scoregraphic
+3  A: 

As Marc said there is no way to do this by default in .Net but adding one yourself is not too difficult.

public class SetOnceValue<T> { 
  private T m_value;
  private bool m_isSet;
  public bool IsSet { get { return m_isSet; }}
  public T Value { get {
    if ( !IsSet ) {
       throw new InvalidOperationException("Value not set");
    }
    return m_value;
  }
  public T ValueOrDefault { get { return m_isSet ? m_value : default(T); }}
  public SetOnceValue() { }
  public void SetValue(T value) {
    if ( IsSet ) {
      throw new InvalidOperationException("Already set");
    }
    m_value = value;
    m_isSet = true;
  }
}

You can then use this as the backing for your particular property.

JaredPar
A: 

You can do this but is not a clear solution and code readability is not the best. If you are doing code design you can have a look at singleton realization in tandem with AOP to intercept setters. The realization is just 123 :)

ruslander