Is there a way of setting a property once only in C#
Asked Answered
L

15

84

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.

Lipoprotein answered 8/5, 2009 at 13:27 Comment(5)
This smells for me, sorry. Why not pass the value in on a constructor? Also, are you going to provide feed back to the caller so they can check before setting the value, to ensure it hasn't been set?Incautious
Ideally I would pass it in the constructor, but I have to build the object up over a period of time. eg one record provides information A, the next record provides information B and C. Once I have a complete set of information, I then use this information to link all the records back together. I wanted a runtime mechanism to verify I only set values once, making them psuedo readonly!Lipoprotein
And yes - it smells for me as well!Lipoprotein
It only smells because you assume it smells. I needed this when a framework I was using called into an object that it instantiated and gave me no way at all to inject dependencies. So, I had my composition root push the container to a write once static property defined on that object.Stilu
C# 9.0 introduced with expressions for records. learn.microsoft.com/en-us/dotnet/csharp/language-reference/…Used
B
62

There is direct support for this in the TPL in .NET 4.0;

(edit: the above sentence was written in anticipation of System.Threading.WriteOnce<T> which existed in the "preview" bits available at the time, but this seems to have evaporated before the TPL hit RTM/GA)

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; } }
Biblicist answered 8/5, 2009 at 13:29 Comment(8)
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.Rosie
Would you mind adding the direct support version of this (since we're way past .NET 4.0 already). I can't seem to find any information regarding this feature.Revolting
It is possible Marc was talking about msdn.microsoft.com/ru-ru/library/hh194820(v=vs.110).aspxVassallo
@DmitryDovgopoly no, I wasn'tBiblicist
@MarcGravell What exactly in the TPL enables this? TaskCompletionSource<T>?Stilu
What were you talking about then?Correa
@ArturoTorresSánchez in the original CTP of the parallel extensions framework (which was "current" when I wrote this, IIRC), there was a WriteOnce<T> type - not related to the dataflow bits. Not sure if this still exists or made it to RTM: but - it existed at time of writing. You can still find traces of them - here, hereBiblicist
@RonnieOverby FYI ^^^Biblicist
R
33

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)
    {
    }
}
Rosie answered 8/5, 2009 at 13:32 Comment(7)
Only you're not setting this.set to true when you set the value... ;)Incumbency
There we go now we are setting it to trueCriticism
Thanks. I saw that, but then started seeing other issues too. I put a more "production ready" version at the bottom of the answer.Rosie
throwIfGet (in the first constructor) isn't defined anywhereCacuminal
Also, the AlreadySetException under the set is called with two values even though the constructor only takes one parameterCacuminal
Ouch on locking for a property getCompressive
Thanks for the code. You should put it up on GitHub/NuGet.Athene
A
20

C# 9 has this feature build in. It is called Init only setters

public DateTime RecordedAt { get; init; }
Anticlerical answered 21/10, 2020 at 15:7 Comment(1)
this is not really the same, since it allows setting value only at initialization and does not create an exception, compilation error insteadMinnie
S
11

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.

Spermary answered 8/5, 2009 at 13:33 Comment(1)
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 variableLipoprotein
C
5

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

Carburize answered 8/5, 2009 at 13:32 Comment(2)
Microsoft should provide such feature.Beefeater
I'm not sure. If something should be set only once, then it probably should be a constructor parameter.Carburize
S
4

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.

Sauternes answered 8/5, 2009 at 13:34 Comment(0)
S
4

Here's my take on this:

public class ReadOnly<T> // or WriteOnce<T> or whatever name floats your boat
{
    private readonly TaskCompletionSource<T> _tcs = new TaskCompletionSource<T>();

    public Task<T> ValueAsync => _tcs.Task;
    public T Value => _tcs.Task.Result;

    public bool TrySetInitialValue(T value)
    {
        try
        {
            _tcs.SetResult(value);
            return true;
        }
        catch (InvalidOperationException)
        {
            return false;
        }
    }

    public void SetInitialValue(T value)
    {
        if (!TrySetInitialValue(value))
            throw new InvalidOperationException("The value has already been set.");
    }

    public static implicit operator T(ReadOnly<T> readOnly) => readOnly.Value;
    public static implicit operator Task<T>(ReadOnly<T> readOnly) => readOnly.ValueAsync;
}

Marc's answer suggests the TPL provides this functionality and I think TaskCompletionSource<T> might have been what he meant, but I can't be sure.

Some nice properties of my solution:

  • TaskCompletionSource<T> is an officially support MS class which simplifies the implementation.
  • You can choose to synchronously or asynchronously get the value.
  • An instance of this class will implicitly convert to the type of value it stores. This can tidy up your code a little bit when you need to pass the value around.
Stilu answered 24/10, 2016 at 0:33 Comment(0)
B
3

Have you considered readonly? http://en.csharp-online.net/const,_static_and_readonly

It's only available to set during init, but might be what you are looking for.

Billibilliard answered 3/1, 2013 at 18:51 Comment(0)
C
3
/// <summary>
/// Wrapper for once inizialization
/// </summary>
public class WriteOnce<T>
{
    private T _value;
    private Int32 _hasValue;

    public T Value
    {
        get { return _value; }
        set
        {
            if (Interlocked.CompareExchange(ref _hasValue, 1, 0) == 0)
                _value = value;
            else
                throw new Exception(String.Format("You can't inizialize class instance {0} twice", typeof(WriteOnce<T>)));
        }
    }

    public WriteOnce(T defaultValue)
    {
        _value = defaultValue;
    }

    public static implicit operator T(WriteOnce<T> value)
    {
        return value.Value;
    }
}
Calotte answered 2/10, 2014 at 14:18 Comment(0)
E
0
interface IFoo {

    int Bar { get; }
}

class Foo : IFoo {

    public int Bar { get; set; }
}

class Program {

    public static void Main() {

        IFoo myFoo = new Foo() {
            Bar = 5 // valid
        };

        int five = myFoo.Bar; // valid

        myFoo.Bar = 6; // compilation error
    }
}

Notice that myFoo is declared as an IFoo, but instantiated as a Foo.

This means that Bar can be set within the initializer block, but not through a later reference to myFoo.

Escalate answered 23/9, 2012 at 18:32 Comment(0)
M
0

The answers assume that objects that receive a reference to an object in the future will not try to change it. If you want to protect against this, you need to make your write-once code only work for types that implement ICloneable or are primitives. the String type implements ICloneable for example. then you would return a clone of the data or new instance of the primitive instead of the actual data.

Generics for primitives only: T GetObject where T: struct;

This is not needed if you know that objects that get a reference to the data will never overwrite it.

Also, consider if the ReadOnlyCollection will work for your application. an exception is thrown whenever a change is attempted on the data.

Moorhead answered 13/11, 2013 at 14:51 Comment(0)
S
0

While the accepted and top-rated answers most directly answer this (older) question, another strategy would be to build a class hierarchy such that you can construct children via parents, plus the new properties:

public class CreatedAtPointA 
{
    public int ExamplePropOne { get; }
    public bool ExamplePropTwo { get; }

    public CreatedAtPointA(int examplePropOne, bool examplePropTwo)
    {
        ExamplePropOne = examplePropOne;
        ExamplePropTwo = examplePropTwo;
    }
}

public class CreatedAtPointB : CreatedAtPointA
{
    public string ExamplePropThree { get; }

    public CreatedAtPointB(CreatedAtPointA dataFromPointA, string examplePropThree) 
        : base(dataFromPointA.ExamplePropOne, dataFromPointA.ExamplePropTwo)
    {
        ExamplePropThree = examplePropThree;
    }
}

By relying on constructors, you can spray some Febreeze on the code smell, though it's still tedious and a potentially expensive strategy.

Silvie answered 23/5, 2018 at 21:8 Comment(0)
M
0

I created a type which allows a value to be set on construction, then after that the value can only be set/overridden once, otherwise an exception is thrown.

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

    public SetOnce(T init) =>
        this.value = init;

    public T Value
    {
        get => this.value;
        set
        {
            if (this.set) throw new AlreadySetException($"Not permitted to override {this.Value}.");
            this.set = true;
            this.value = value;
        }
    }

    public static implicit operator T(SetOnce<T> setOnce) =>
        setOnce.value;

    class AlreadySetException : Exception
    {
        public AlreadySetException(string message) : base(message){}
    }
}
Mite answered 16/3, 2019 at 1:42 Comment(0)
D
0

Since it seems to be the prime subject of all duplicate related post about setting only once a property, I post my custom solution here.

To be brief, I took advantage of source generator to automatically generate back-end code of WriteOnce<T> a-like. (mine is called SettableNTimesProperty<T> but have kind of same purpose).

This end up being used like following.

Lets say you have this DTO class :

internal class DTO
{
    int ID { get; init; }
    string Name { get; init; }

    public DTO(int id, string name = "Default_DTO_Name")
    {
        ID = id;
        Name = name;
    }
}

In order to make its properties settable only once instead of using init, modify your code this way :

internal partial class DTO : IDTO
{
    public DTO(int id, string name = "Default_DTO_Name")
    {
        ((IDTO)this).ID = id;
        ((IDTO)this).Name = name;
    }
}

and add this interface

using SetOnceGenerator;
public interface IDTO
{
    [SetOnce]
    int ID { get; set; }

    [SetOnce]
    string Name { get; set; }
}  

If you want to allow multiple set, up to n times maximum, use [SetNTimes(n)] attribute instead of [SetOnce]

You can check it here.

Damocles answered 6/9, 2023 at 0:7 Comment(0)
D
-2

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 :)

Decorator answered 8/5, 2009 at 13:49 Comment(1)
That's why link-only answers are off-topic... the intercept link is dead :-(Judyjudye

© 2022 - 2024 — McMap. All rights reserved.