C# 在构造函数外部设置只读字段的可接受方法

C# 在构造函数外部设置只读字段的可接受方法,c#,constructor,switch-statement,readonly,C#,Constructor,Switch Statement,Readonly,我有一个构造函数,它对交换机执行初始化,如下所示: class Foo { public readonly int Bar; public readonly object Baz; public Foo(int bar, string baz) { this.Bar = bar; switch (bar) { case 1: // Boatload of initialization c

我有一个构造函数,它对交换机执行初始化,如下所示:

class Foo {
    public readonly int Bar; 
    public readonly object Baz; 

    public Foo(int bar, string baz) { 
        this.Bar = bar; 
        switch (bar) { 
        case 1: 
            // Boatload of initialization code
            this.Bar = /* value based upon initialization code */
            this.Baz = /* different value based upon initialization code */
        case 2:
            // Different boatload of initialization code
            this.Bar = /* value based upon initialization code */
            this.Baz = /* different value based upon initialization code */
        case 3: 
            // Yet another...
            this.Bar = /* value based upon initialization code */
            this.Baz = /* different value based upon initialization code */ 
        default: 
            // handle unexpected value 
        } 
    }
}
我仍在实现这一点,但一旦完成,很容易就会有几百行。我不喜欢有这么大的构造函数,但我不知道如何安全地绕过这个语言特性(而绕过是我不想做的事情)。也许应该是一个暗示,我正在尝试做的事情有一些根本性的错误,但我不确定

基本上,我想在我自己的自定义不可变类型中执行复杂的初始化。最好的方法是什么?在这种情况下,数不清的行计数构造函数是一件可怕的事情吗

更新: 为了澄清,我想做的是在一个类中保持不变性,该类将以复杂的方式以尽可能最好的方式初始化实例。我正在编写一个表示随机生成的标记的类,
FormatToken
,它通常是一个字符

复杂的初始化是解析一个格式字符串(注意,我并不是试图解析一个regexp来生成一个随机字符串,我不想在接下来的20年中这样做:)。我最初写的东西是通过构造函数参数接受输入的,比如

+        /// Format tokens
+        /// c{l} Lowercase Roman character in the ASCII range. 
+        /// v{L} Uppercase Roman character in the ASCII range. 
+        /// c Roman character in the ASCII range.
+        /// d Decimal.
+        /// d{0-9} Decimal with optional range, both minimum and maximum inclusive.    

var rand = new RandomString("c{l}C{L}ddd{0-4}d{5-9}"); 
rand.Value == /* could equal "fz8318" or "dP8945", but not "f92781". 
最终产生这个问题的类是表示每个标记的类。初始化问题来自于能够支持各种格式(ASCII字符、罗马字母、小数、符号等)

这是所讨论的实际代码:

internal class FormatToken {
    public TokenType Specifier { get; private set; }  
    public object Parameter { get; private set; }  

    public FormatToken(TokenType _specifier, string _parameter) { 
        // discussion of this constructor at 
        // http://stackoverflow.com/questions/19288131/acceptable-way-to-set-readonly-field-outside-of-a-constructor/
        Specifier = _specifier; 
        _init(_specifier, _parameter); 
    }

    private void _init(TokenType _specifier, string _parameter) { 
        switch (_specifier) { 
        case TokenType.Decimal:
            _initDecimalToken(_parameter); 
            break;
        case TokenType.Literal:
            Parameter = _parameter; 
            break; 
        case TokenType.Roman:
        case TokenType.LowerRoman:
        case TokenType.UpperRoman:
            _initRomanToken(_specifier, _parameter); 
            break;
        default: 
            throw new ArgumentOutOfRangeException("Unexpected value of TokenType."); 
        }
    }
最初我使用了
readonly
,因为我误解了使用它的原因。只需删除
readonly
并替换为auto属性(即
{get;private set;}
)就可以解决我的不变性问题

这个问题更多的是关于初始化任务的问题,而不是关于
FormatToken
的不变性的问题。也许“如何执行复杂的、可能未知的初始化”现在是一个更好的问题。现在我完全清楚,拥有一个巨大的开关是一个坏主意。工厂模式肯定是内置的因为我正在做的事情,我想这回答了我的问题。我只想再给它几天时间


非常感谢您到目前为止的想法!我把最初的示例代码留在这里,以使答案有意义。

如果没有更多信息,很难判断是否存在根本错误,但我看起来并不是完全错误(根据显示的事实)。我会用自己的方法或对象来处理每种情况(取决于表单内容)。当然,您不能使用
readonly
,而是使用
public int Bar{get;private set;}
public object Baz{get;private set;}
的属性

public Foo(int bar, string baz) { 
     this.Bar = bar; 
     switch (bar) { 
        case 1: 
            methodFoo();
        case 2:
            methodBar();
        case 3: 
            methodFooBar();
        default: 
            ExceptionHandling();
}
您可以使用:

public int Bar{get;private set;}
。您已经将
Bar
大写,就像它是一个属性一样。其他类可以获取
Bar
,但只有您的类可以设置
Bar
,因为它是
私有集;
setter

但是,可以为每个对象多次设置
Bar
的值


如果按照构造函数Micha的方式()执行,则可以在方法中设置自动属性(但不能使用
readonly
)。

switch语句根本错误

有关说明,请参见:

还有一条路要走:(基类)

还有另一条路要走:(工厂)

您可以在Foo类上结合使用静态工厂方法和私有构造函数。工厂方法应该负责执行大型切换,计算出所需的Bar和Baz值,然后将计算出的值传递给私有构造函数

当然,这并没有消除巨型开关,但它将其完全移出构造函数,在构造函数中,我们通常被告知进行大型计算是不好的

这样的话,你最终会得到

class Foo {
    public readonly int Bar; 
    public readonly object Baz; 

    private Foo(int bar, string baz) { 
        this.Bar = bar; 
        this.Bas = baz;
    }

    public static Foo CreateFoo(int bar, string baz)
    {
        int tbar;
        string tbaz;
        switch (bar) { 
        case 1: 
            // Boatload of initialization code
            tbar = /* value based upon initialization code */
            tbaz = /* different value based upon initialization code */
        case 2:
            // Different boatload of initialization code
            tbar = /* value based upon initialization code */
            tbaz = /* different value based upon initialization code */
        //...
        default: 
            // handle unexpected value 
        }
        return new Foo(tbar, tbaz);
    }
}

也许我没抓住重点,但你认为呢:

class Foo
{
    public readonly int Bar;
    public readonly object Baz;

    public Foo(int bar, string baz) { 
        this.Bar = GetInitBar(bar); 
    }

    private int GetInitBar(int bar)
    {
        int result;
         switch (bar) { 
            case 1: 
                // Boatload of initialization code
                result = /* value based upon initialization code */
                result = /* different value based upon initialization code */
            case 2:
                // Different boatload of initialization code
                result = /* value based upon initialization code */
                result = /* different value based upon initialization code */
            case 3: 
                // Yet another...
                result = /* value based upon initialization code */
                result = /* different value based upon initialization code */ 
            default: 
                // handle unexpected value 
        }
        return result;
    }
}

拉斯穆斯格雷夫和乔恩·斯基特的后续行动:

class Foo
{
  public readonly int Bar; 
  public readonly object Baz; 

  private Foo(int bar, string baz) { 
      this.Bar = bar; 
      this.Baz = baz;
  }

  private static Foo _initDecimalToken(string _parameter)
  {
    int calculatedint = 0;
    string calculatedstring = _parameter;
    //do calculations
    return new Foo(calculatedint, calculatedstring);
  }
  private static Foo _initRomanToken(int bar, string _parameter)
  {
    int calculatedint = 0;
    string calculatedstring = _parameter;
    //do calculations
    return new Foo(calculatedint, calculatedstring);
  }
  public static Foo CreateFoo(int bar, string baz)
  {
    switch (bar) 
    { 
      case 1:
        return _initDecimalToken(baz);
      case 2:
        return _initRomanToken(bar, baz);
      default: 
        // handle unexpected value...
        return null;
    }
  }
}

如果您想保持Foo的轻量级,可以将静态构造函数放入一个单独的类中(例如FooMaker)

我也更愿意接受Nahum的答案,因为如果您想扩展作为其中一部分的行为,开关语句将无法实现打开/关闭原则。要回答的另一部分是如何解决这个问题。这可以通过继承方法和工厂方法来实现()创建适当的实例并对成员进行惰性初始化()

    class FooFactory
    {
        static Foo CreateFoo(int bar,string baz)
        {
              if(baz == "a")
                  return new Foo1(bar,baz);
              else if(baz == "b")
                  return new Foo2(bar,baz);
              ........
        }
    }

    abstract class Foo
    {
          public int bar{get;protected set;}
          public string baz{get;protected set;}
          //this method will be overriden by all the derived class to do
          //the initialization
          abstract void Initialize();
    }
让Foo1和Foo2从Foo派生并重写Initialize方法以提供适当的实现。因为我们需要先初始化Foo中的其他方法才能工作,所以我们可以在Initialize方法中将bool变量设置为true,在其他方法中,我们可以检查此值是否设置为true,否则我们可以抛出n表示需要通过调用Initialize方法初始化对象的异常

现在,客户机代码将如下所示

   Foo obj = FooFactory.CreateFoo(1,"a");
   obj.Initialize();
   //now we can do any operation with Foo object.

如果我们在类中使用静态方法,会出现一个问题,即如果需要,这些方法无法访问实例成员。因此,我们可以将其作为工厂方法分离出来,以创建实例,而不是在同一类中使用静态方法(是的,虽然Singleton是这样工作的,但我更强调这里提到的当前行为的这种行为,因为它访问其他适当的静态方法来完成它的工作)。

我认为Thomas的方法是最简单的,并且维护了jdphenix已经拥有的构造函数API

另一种方法是使用
Lazy
将设置推迟到使用值时。我喜欢使用
Lazy
class Foo {
    public readonly Lazy<int> Bar; 
    public readonly Lazy<object> Baz; 

    public Foo(int bar, string baz) { 
        this.Bar = new Lazy<int>(() => this.InitBar(bar));
        this.Baz = new Lazy<object>(() => this.InitBaz(bar));
    }

    private int InitBar(int bar)
    {
        switch (bar) { 
        case 1: 
            // Bar for case 1
        case 2:
            // Bar for case 2
        case 3: 
            // etc..
        default: 
        }
    }

    private object InitBaz(int bar)
    {
        switch (bar) { 
        case 1: 
            // Baz for case 1
        case 2:
            // Baz for case 2
        case 3: 
            // etc..
        default: 
        }
    }
}
void Main() {
    MyImmutable o = new MyImmutable(new MyMutable { Message = "hello!", A = 2});
    Console.WriteLine(o.Value.A);//prints 3
    o.Value.IncrementA();        //compiles & runs, but mutates a copy
    Console.WriteLine(o.Value.A);//prints 3 (prints 4 when Value isn't readonly)
    //o.Value.B = 42;            //this would cause a compiler error.
    //Consume(ref o.Value.B);    //this also causes a compiler error.
}
struct MyMutable {
    public string Message;
    public int A, B, C, D;
    //avoid mutating members such as the following:
    public void IncrementA() { A++; } //safe, valid, but really confusing...
}
class MyImmutable{
    public readonly MyMutable Value;
    public MyImmutable(MyMutable val) {
        this.Value=val;
        Value.IncrementA();
    }
}
void Consume(ref int variable){}
var v2 = o.Value;
v2.D = 42;
var d = new MyImmutable(v2);