简体   繁体   中英

class properties that can be assigned only when creating object?

It's possible in C# to write things such way:

Instrument instr = new Instrument { ClassCode = "Hello", Ticker = "World" };

However to do that you have to add set; in the corresponding class:

class Instrument
{
    public string ClassCode { get; set; }
    public string Ticker { get; set; }
}

This means that later someone can accidentally change value:

instr.ClassCode.set = "Destroy"

And I don't want to allow that. Ie, from one hand I want a readonly property, from another hand i want to create objects like that:

Instrument instr = new Instrument { ClassCode = "Hello", Ticker = "World" };

I'm not sure if this is possible. Probably I should use fields or something else instead of properties. I only want to have the syntax of the last sentence but keep things readonly at the same time.

upd: In short no, readonly properties are not allowed in any way. Regular constructor and "get" should be used in this case.

I would accomplish this by using private setters, and calling them from a constructor. This way you can control when the setters are called.

Instrument instr = new Instrument("Hello", "World");

class Instrument
{
    public Instrument(string ClassCode, string Ticker) 
    {
        this.ClassCode = ClassCode;
        this.Ticker = Ticker;
    }

    public string ClassCode { get; private set; }
    public string Ticker { get; private set; }
}

This happens because the code you're using:

Instrument instr = new Instrument { ClassCode = "Hello", Ticker = "World" };

is just syntactic sugar for this:

Instrument instr = new Instrument();
instr.ClassCode = "Hello";
instr.Ticker = "World";

The two samples above are exactly the same, the former is just shorthand for the latter.

What you want to achieve this functionality is to make these values private. Something like this:

class Instrument
{
  private string _classCode;
  private string _ticker;
  public string ClassCode{ get { return _classCode; } }
  public string Ticker{ get { return _ticker; } }
  private ClassCode() {}
  public ClassCode(string classCode, string ticker)
  {
    _classCode = classCode;
    _ticker = ticker;
  }
}

What you have here are:

  • Private values which can be set only from within the class, thus they can't be overridden by someone else later.
  • Public read-only properties to read those values.
  • A private default constructor so that nobody can instantiate this object without supplying necessary values.
  • A public constructor which requires the necessary values.

You'd instantiate it like this:

Instrument instr = new Instrument("Hello", "World");

This means that you wouldn't be able to use the syntactic sugar (object initializer, I think it's called) to instantiate the class anymore, you'd have to use the constructor. So this would be a breaking change for the current implementation, but is a simple way to produce the desired functionality.

If you're bent on the initializer syntax, a typical way to do this is to create "popsicle" objects, objects which can be fluid to begin with, but then you freeze them and changes are locked.

Basically, I would create the class like this (test this with LINQPad ):

void Main()
{
    Instrument instr1 = new Instrument
    {
        ClassCode = "Hello",
        Ticker = "World"
    };
    instr1.ClassCode = "123";              // is allowed

    Instrument instr2 = new Instrument
    {
        ClassCode = "Hello",
        Ticker = "World"
    }.Freeze();                            // <-- notice Freeze here
    instr2.ClassCode = "123";              // throws InvalidOperationException
}

public class Instrument
{
    private string _ClassCode;
    private string _Ticker;
    private bool _IsFrozen;

    public string ClassCode
    {
        get { return _ClassCode; }
        set
        {
            ThrowIfFrozen();
            _ClassCode = value;
        }
    }

    public string Ticker
    {
        get { return _Ticker; }
        set
        {
            ThrowIfFrozen();
            _Ticker = value;
        }
    }

    private void ThrowIfFrozen()
    {
        if (_IsFrozen)
            throw new InvalidOperationException(
                "Instrument object has been frozen");
    }

    public Instrument Freeze()
    {
        _IsFrozen = true;
        return this;
    }

    public bool IsFrozen
    {
        get
        {
            return _IsFrozen;
        }
    }
}

Well, kind of possible :)

make set private and create public constructor so that you can pass ClassCode and Ticker, ie:

class Instrument
{
    public string ClassCode { get; private set; }
    public string Ticker { get; private set; }

    public Instrument(ClassCode classCode, Ticker ticker)
    {
         ClassCode = classCode;
         Ticker = ticker
    }
}

Assuming you are only setting the value through the constructor, you could explicitly write the set method to check to see if the field it assigns to is unassigned (null or whatever) and only write the value in the set if it is unassigned. That way your set is effectively write-once. It would get most of the way there.

class Instrument
{
     private string _classCode;
     private string _ticker;

     public string ClassCode
     {
        get
        {
          return _classCode;
         }
        set
        {
           if (_classCode == null)
              _classCode = value;
        }
      }
     public string Ticker     {
        get
        {
          return _ticker;
         }
        set
        {
           if (_ticker == null)
              _ticker = value;
        }
      }


}

One standard solution is to accept the initial values of your properties in the constructor - something like this:

class Instrument
{
  public string ClassCode {get; private set}
  public Instrument (string classCode)
  {
    this.ClassCode = classCode;
  }
}

Add a constructor and make them properties that return a variable:

class Instrument
{
    private string classCode;
    private string ticker;
    Instrument(string classCode, string ticker)
    {
        this.classCode = classCode;
        this.ticker = ticker;
    }
    public string ClassCode
    {
         get { return classCode; }
    }
    public string Ticker
    {
          get { return ticker; }
    }
}

Then I believe you want to use a constructor rather than initialization.

If your class is

class Instrument
{

    private string _ClassCode;
    private string _Ticker;

    public Instrument(string ClassCode, string Ticker)
{
_ClassCode = ClassCode;
_Ticker = Ticker;
}
    public string ClassCode { get {return _ClassCode;}}
    public string Ticker { get {return _Ticker;} }
}

you will get the desired effect.

OR

You can create separate properties with different access levels like:

private string _ClassCode;
      internal string ClassCodeSet
      {
         set { _ClassCode = value; }
      }
      public string ClassCode
      {
         get { return _ClassCode; }
      }

Then use a friendly class to initialize the value (per your original code) and the public property to read the value.

If you want to have nice syntax then you can use default values for your constuctor

class Instrument {
    public Instrument(string ClassCode = null, string Ticker = null)
    {
        this.ClassCode = ClassCode;
        this.Ticker = Ticker;
    }
    public string ClassCode { get; private set; } 
    public string Ticker { get; private set; } 
}

then you can create objects in your code like this:

Instrument instrument = new Instrument(ClassCode: "classCode", Ticker: "ticker");

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM