有没有办法with
在C#中模拟F#的关键字?我知道它可能不会那么优雅,但我想知道是否有办法处理创建新的不可变数据结构副本.
F#中的记录详述.
这是我正在尝试做的一个例子.我们将通过接口创建"不可变"的数据视图,同时保持具体类的可变性.这让我们可以在本地进行变异(在工作时),然后返回一个不可变的接口.这就是我们在C#中处理不变性的问题.
public interface IThing
{
double A { get; }
double B { get; }
}
public class Thing : IThing
{
double A { get; set; }
double B { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
但是,当需要对数据进行更改时,来回转换它的类型(或可变性!)是不安全的,将类的每个属性手动转换为新实例也是一种痛苦.如果我们添加一个新的怎么办?我是否必须追踪每次操纵?我真的只需要,我不想创造未来的头痛what I had before, but with [some change]
.
例:
// ...
IThing item = MethodThatDoesWork();
// Now I want to change it... how? This is ugly and error/change prone:
IThing changed = new Thing {
A = item.A,
B = 1.5
};
// ...
Run Code Online (Sandbox Code Playgroud)
什么是实现这一目标的合理策略?你过去用过什么?
由于没有语法糖,我知道你必须要么:
至少这是我现在能想到的.
我不认为最后两个是个好主意,因为你带来了大机器来解决一个非常容易的问题.
是的,当你有数千个数据结构时,你可能会重新考虑这个,但如果你只有几个,我就不会使用它.
所以剩下的基本上是智能构造函数和类似的东西 - 这是一个如何做到这一点的简单示例(注意你并不真的需要所有这些 - 选择) - 它基本上错过了使用null
/ nullable
寻找你需要什么 - 更好的选择可能是重载或像Option<T>
数据类型的东西,但现在我认为你得到它:
class MyData
{
private readonly int _intField;
private readonly string _stringField;
public MyData(int intField, string stringField)
{
_intField = intField;
_stringField = stringField;
}
public MyData With(int? intValue = null, string stringValue = null)
{
return new MyData(
intValue ?? _intField,
stringValue ?? _stringField);
}
// should obviously be put into an extension-class of some sort
public static MyData With(/*this*/ MyData from, int? intValue = null, string stringValue = null)
{
return from.With(intValue, stringValue);
}
public int IntField
{
get { return _intField; }
}
public string StringField
{
get { return _stringField; }
}
}
Run Code Online (Sandbox Code Playgroud)
要添加到Carsten的正确答案,在C#中无法做到这一点,因为它不在语言中.在F#中,它是一种语言特性,简洁的记录声明语法扩展到相当多的IL.C#没有那种语言功能(还).
这是我不再喜欢在C#中工作的原因之一,因为与在F#中做同样的事情相比,开销太大了.尽管如此,有时候我必须出于某种原因在C#中工作,当发生这种情况时,我会咬紧牙关并手工编写记录.
例如,整个AtomEventSource库是用C#编写的,但是带有不可变的记录.这是AtomLink类的缩写示例:
public class AtomLink : IXmlWritable
{
private readonly string rel;
private readonly Uri href;
public AtomLink(string rel, Uri href)
{
if (rel == null)
throw new ArgumentNullException("rel");
if (href == null)
throw new ArgumentNullException("href");
this.rel = rel;
this.href = href;
}
public string Rel
{
get { return this.rel; }
}
public Uri Href
{
get { return this.href; }
}
public AtomLink WithRel(string newRel)
{
return new AtomLink(newRel, this.href);
}
public AtomLink WithHref(Uri newHref)
{
return new AtomLink(this.rel, newHref);
}
public override bool Equals(object obj)
{
var other = obj as AtomLink;
if (other != null)
return object.Equals(this.rel, other.rel)
&& object.Equals(this.href, other.href);
return base.Equals(obj);
}
public override int GetHashCode()
{
return
this.Rel.GetHashCode() ^
this.Href.GetHashCode();
}
// Additional members removed for clarity.
}
Run Code Online (Sandbox Code Playgroud)
除了不必键入这一切的开销,它也一直困扰着我,如果你正在做的(教条)测试驱动开发(你不有到),你要测试这些方法,以及.
但是,使用AutoFixture和SemanticComparison等工具,可以使它具有一定的声明性.这是AtomLinkTests的一个例子:
[Theory, AutoAtomData]
public void WithRelReturnsCorrectResult(
AtomLink sut,
string newRel)
{
AtomLink actual = sut.WithRel(newRel);
var expected = sut.AsSource().OfLikeness<AtomLink>()
.With(x => x.Rel).EqualsWhen(
(s, d) => object.Equals(newRel, d.Rel));
expected.ShouldEqual(actual);
}
Run Code Online (Sandbox Code Playgroud)
在这里,它仍然相对冗长,但您可以轻松地将其重构为通用方法,以便每个测试用例成为一个单行.
它仍然是一个麻烦,所以即使你用C#编写大部分代码,你也可以考虑在一个单独的F#库中定义你的不可变类型.从C#看,F#记录看起来像上面的'普通'不可变类AtomLink
.与其他一些F#类型(如歧视联盟)相反,F#记录完全可以从C#中获取.