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