How to implement properties which can be set only one time

Viewed 148

I want to implement some properties that can be set only one time. I am aware of readonly but i want to be able to set the value from outside the class.

The situation is the following: I have a class which i use as a key for some data i read from database. This key-class contains several properties.

My first idea was to make multiple constructors where i can set the properties like i need them but since "not set" is a valid state for most of these properties i soon had a bunch of constructors and i decided this is not the best way.

I can't make the properties with public setters because they are key information and it's no good to change them afterwards. So i need a way to set them once from outside the key-class because i don't want to make 20 slightly diffrent constructors for every case.

Is there some readonly-like easy to go method to do this or will i have to implement it myself?

5 Answers

As also commented by @Fildor, the Builder pattern may be appropriate here. You can set the properties on your builder class as often as you want, there is no harm in allowing multiple assignment there. But once you use that to build your class, then it's locked, then no more modifications are allowed.

class MyClass {
  internal MyClass(MyClassBuilder builder) {
     MyProp1 = builder.MyProp1;
     MyProp2 = builder.MyProp2;
     MyProp3 = builder.MyProp3;
  }
  public string MyProp1 { get; }
  public string MyProp2 { get; }
  public string MyProp3 { get; }
}

class MyClassBuilder {
  public string MyProp1 { get; set; }
  public string MyProp2 { get; set; }
  public string MyProp3 { get; set; }
  public MyClass Build() => new MyClass(this);
}

Usage: new MyClassBuilder { MyProp2 = "abc" }.Build(). Different APIs are also possible based on the same general idea.

The builder patter by hvd is really nice, and it is what you should most likely use. However, here are my 2 cents. I like somewhat how JS works at times, and here I try to emulate it. This is basically using a dictionary as a configuration collection, containing the names of the properties and the values needed.

public class Key
{
    private readonly string Property1;
    private readonly string Property2;
    private readonly string Property3 = "!";

    public Key(Dictionary<string, object> config)
    {
        if (config == null)
        {
            return;
        }

        var type = typeof(Key);
        var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        foreach (string name in config.Keys)
        {
            var field = fields.Where(f => f.Name == name).SingleOrDefault();
            if (field == null)
            {
                continue;
            }

            field.SetValue(this, config[name]);
        }
    }

    public string GetValues()
    {
        return Property1 + Property2 + Property3;
    }
}

And you can use it as:

var key = new Key(new Dictionary<string, object>()
    {
        { "Property1", "Hi, " },
        { "Property2", "There" }
    }
);

Console.WriteLine(key.GetValues()); // Hi, There!

One way to go about this is to have properties with public setter and have a flag for each one, first time the setter is called set flag to true and return from the setter by checking the flag in subsequent calls.

If you have too many properties use a dictionary instead where you map properties to states (set/unset).

Try something like this (basically what @SelmanGenc was suggesting):

public class Key {

private ConcurrentDictionary<string, bool> _setProps = new ConcurrentDictionary<string, bool>();
private string _prop1;

public string Prop1 {

get { return _prop1; }
set { 
    if(!_setProps.ContainsKey(nameof(Prop1)){
        _setProps.Add(nameof(Prop1), true);
        _prop1 = value;
    }
  }
 }
}

Have a private property that know if its been set? something like:

public class MyClass
{
   private bool myValueSet = false;
   private string _myValue;

   public string MyValue
   {
      set {
        if (!myValueSet)
        {
          _myValue = value;
        }
      }
      get {
        return _myValue; 
      }
   }
}
Related