在C#中模拟F#`with`关键字

joc*_*ull 7 c# f#

有没有办法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)

什么是实现这一目标的合理策略?你过去用过什么?

Car*_*ten 9

由于没有语法糖,我知道你必须要么:

  • 手工完成(见下文)
  • 使用一些反射/自动化器(不是这个的粉丝)
  • 使用一些AOP技术(既不是那些人的粉丝)

至少这是我现在能想到的.

我不认为最后两个是个好主意,因为你带来了大机器来解决一个非常容易的问题.

是的,当你有数千个数据结构时,你可能会重新考虑这个,但如果你只有几个,我就不会使用它.

所以剩下的基本上是智能构造函数和类似的东西 - 这是一个如何做到这一点的简单示例(注意你并不真的需要所有这些 - 选择) - 它基本上错过了使用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)


Mar*_*ann 6

要添加到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)

除了不必键入这一切的开销,它也一直困扰着我,如果你正在做的(教条)测试驱动开发(你不到),你要测试这些方法,以及.

但是,使用AutoFixtureSemanticComparison等工具,可以使它具有一定的声明性.这是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#中获取.