Sed*_*glu 157 c# immutability data-structures
我有这个API函数:
public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d,
string e, string f, out Guid code)
Run Code Online (Sandbox Code Playgroud)
我不喜欢它.因为参数顺序变得不必要的重要.添加新字段变得更加困难.很难看到传递的是什么.将方法重构为较小的部分更加困难,因为它会产生另一个传递子函数中所有参数的开销.代码更难阅读.
我提出了一个最明显的想法:让一个对象封装数据并传递它,而不是逐个传递每个参数.这是我想出的:
public class DoSomeActionParameters
{
public string A;
public string B;
public DateTime C;
public OtherEnum D;
public string E;
public string F;
}
Run Code Online (Sandbox Code Playgroud)
这减少了我的API声明:
public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)
Run Code Online (Sandbox Code Playgroud)
尼斯.看起来很无辜,但我们实际上引入了一个巨大的变化:我们引入了可变性.因为我们以前一直在做的事实上是传递一个匿名的不可变对象:堆栈上的函数参数.现在我们创建了一个非常可变的新类.我们创建了操纵调用者状态的能力.太糟糕了.现在我希望我的对象不可变,我该怎么办?
public class DoSomeActionParameters
{
public string A { get; private set; }
public string B { get; private set; }
public DateTime C { get; private set; }
public OtherEnum D { get; private set; }
public string E { get; private set; }
public string F { get; private set; }
public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d,
string e, string f)
{
this.A = a;
this.B = b;
// ... tears erased the text here
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我实际上重新创建了我原来的问题:参数太多了.很明显,这不是要走的路.我该怎么办?实现这种不变性的最后一个选择是使用这样的"只读"结构:
public struct DoSomeActionParameters
{
public readonly string A;
public readonly string B;
public readonly DateTime C;
public readonly OtherEnum D;
public readonly string E;
public readonly string F;
}
Run Code Online (Sandbox Code Playgroud)
这允许我们避免具有太多参数的构造函数并实现不变性.实际上它修复了所有问题(参数排序等).然而:
那时我感到困惑并决定写下这个问题:在没有引入可变性的情况下,C#中最直接的方法是避免"太多参数"问题?是否有可能为此目的使用readonly结构,但没有错误的API设计?
澄清:
UPDATE
这里提供的答案有不同的优点/缺点.因此,我想将其转换为社区维基.我认为代码示例和优点/缺点的每个答案都可以为将来的类似问题提供一个很好的指导.我现在正试图找出如何做到这一点.
Sam*_*eff 83
使用构建器和特定于域的语言样式API - Fluent Interface的组合.API更加冗长,但是通过intellisense,它可以非常快速地输入并且易于理解.
public class Param
{
public string A { get; private set; }
public string B { get; private set; }
public string C { get; private set; }
public class Builder
{
private string a;
private string b;
private string c;
public Builder WithA(string value)
{
a = value;
return this;
}
public Builder WithB(string value)
{
b = value;
return this;
}
public Builder WithC(string value)
{
c = value;
return this;
}
public Param Build()
{
return new Param { A = a, B = b, C = c };
}
}
DoSomeAction(new Param.Builder()
.WithA("a")
.WithB("b")
.WithC("c")
.Build());
Run Code Online (Sandbox Code Playgroud)
Teo*_*gul 21
框架中包含的一种风格通常就像将相关参数分组到相关类中一样(但是又有问题可变性):
var request = new HttpWebRequest(a, b);
var service = new RestService(request, c, d, e);
var client = new RestClient(service, f, g);
var resource = client.RequestRestResource(); // O params after 3 objects
Run Code Online (Sandbox Code Playgroud)
Mar*_*ann 10
你所拥有的是一个非常明确的迹象表明所讨论的类违反了单一责任原则,因为它有太多的依赖关系.寻找将这些依赖项重构为Facade Dependencies集群的方法.
Jef*_*dge 10
只需将参数数据结构从a更改class
为a struct
,就可以了.
public struct DoSomeActionParameters
{
public string A;
public string B;
public DateTime C;
public OtherEnum D;
public string E;
public string F;
}
public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)
Run Code Online (Sandbox Code Playgroud)
该方法现在将获得自己的结构副本.方法无法观察对参数变量所做的更改,并且调用方无法观察到方法对变量的更改.在没有不变性的情况下实现隔离.
优点:
缺点:
如何在数据类中创建构建器类.数据类将所有setter设置为private,只有构建器才能设置它们.
public class DoSomeActionParameters
{
public string A { get; private set; }
public string B { get; private set; }
public DateTime C { get; private set; }
public OtherEnum D { get; private set; }
public string E { get; private set; }
public string F { get; private set; }
public class Builder
{
DoSomeActionParameters obj = new DoSomeActionParameters();
public string A
{
set { obj.A = value; }
}
public string B
{
set { obj.B = value; }
}
public DateTime C
{
set { obj.C = value; }
}
public OtherEnum D
{
set { obj.D = value; }
}
public string E
{
set { obj.E = value; }
}
public string F
{
set { obj.F = value; }
}
public DoSomeActionParameters Build()
{
return obj;
}
}
}
public class Example
{
private void DoSth()
{
var data = new DoSomeActionParameters.Builder()
{
A = "",
B = "",
C = DateTime.Now,
D = testc,
E = "",
F = ""
}.Build();
}
}
Run Code Online (Sandbox Code Playgroud)
为什么不只是创建一个强制不变性的界面(即只有getter)?
它本质上是您的第一个解决方案,但您强制该函数使用该接口来访问该参数.
public interface IDoSomeActionParameters
{
string A { get; }
string B { get; }
DateTime C { get; }
OtherEnum D { get; }
string E { get; }
string F { get; }
}
public class DoSomeActionParameters: IDoSomeActionParameters
{
public string A { get; set; }
public string B { get; set; }
public DateTime C { get; set; }
public OtherEnum D { get; set; }
public string E { get; set; }
public string F { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
并且函数声明变为:
public ResultEnum DoSomeAction(IDoSomeActionParameters parameters, out Guid code)
Run Code Online (Sandbox Code Playgroud)
优点:
struct
解决方案那样的堆栈空间问题缺点:
DoSomeActionParameters
是一个可以映射到的类IDoSomeActionParameters
我不是C#程序员,但我相信C#支持命名参数:( F#和C#在很大程度上可以兼容这类东西)它确实:http: //msdn.microsoft.com/en-us/library/dd264739的.aspx#Y342
所以调用原始代码变为:
public ResultEnum DoSomeAction(
e:"bar",
a: "foo",
c: today(),
b:"sad",
d: Red,
f:"penguins")
Run Code Online (Sandbox Code Playgroud)
这不需要更多的空间/思考你的对象创建和所有好处,事实上你根本没有改变在unerlying系统中发生的事情.您甚至不需要重新编码任何内容来指示参数已命名
编辑:这是我发现的关于它的艺术品. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ 我应该提到C#4.0支持命名参数,3.0没有