C# 在C语言中用`关键字模拟F#`#

C# 在C语言中用`关键字模拟F#`#,c#,f#,C#,F#,有没有办法用C中的关键字来模拟F的?我知道它可能不会那么优雅,但我想知道是否有任何方法可以处理创建数据结构的新的不可变副本 F#中的记录是 这是一个我正在尝试做的例子。我们将通过接口创建数据的“不可变”视图,同时维护具体类中的可变性。这允许我们在本地(工作时)进行变异,然后返回一个不可变的接口。这就是我们在C#中处理的不变性 然而,当需要对数据进行更改时,来回转换数据的类型(或易变性!)并不安全,手动将类的每个属性转换为新实例也是一件非常痛苦的事情。如果我们添加一个新的呢?我必须追踪每一次操作吗

有没有办法用C中的关键字来模拟F的
?我知道它可能不会那么优雅,但我想知道是否有任何方法可以处理创建数据结构的新的不可变副本

F#中的记录是

这是一个我正在尝试做的例子。我们将通过接口创建数据的“不可变”视图,同时维护具体类中的可变性。这允许我们在本地(工作时)进行变异,然后返回一个不可变的接口。这就是我们在C#中处理的不变性

然而,当需要对数据进行更改时,来回转换数据的类型(或易变性!)并不安全,手动将类的每个属性转换为新实例也是一件非常痛苦的事情。如果我们添加一个新的呢?我必须追踪每一次操作吗?当我真的只需要
我以前拥有的东西,但需要[一些改变]
的时候,我不想再让人头疼了

例如:

// ...

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

// ...

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

因为我知道没有语法上的糖分,你必须:

  • 手工操作(见下文)
  • 使用一些反射/自动映射(不喜欢这个)
  • 使用一些AOP技术(两者都不喜欢)
至少这是我现在能想到的

我不认为最后两个是个好主意,因为你用大型机器来解决一个非常简单的问题

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

所以剩下的基本上是智能构造函数和类似的东西-这里是一个简单的例子,说明如何做到这一点(请注意,您并不真的需要所有这些-选择和选择)-基本上,使用
null
/
null
来查找所需内容是错误的-更好的选择可能是重载或类似于
选项的数据类型,但现在我认为您已经得到了:

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

再加上卡斯滕的正确答案,用C#无法做到这一点,因为C#不是语言。在F#中,这是一种语言特性,其中。C#还没有那种语言特性

这是我不再喜欢在C#中工作的原因之一,因为与在F#中做同样的事情相比,有太多的开销。尽管如此,有时出于这样或那样的原因,我不得不在C#中工作,当这种情况发生时,我咬紧牙关,用手写下记录

例如,整个库都是用C#编写的,但记录是不可变的。以下是一个简短的示例:

除了必须键入所有这些内容的开销之外,如果您正在进行(教条式的)测试驱动开发(您不必这样做),您还需要测试这些方法,这也一直困扰着我

不过,使用诸如和之类的工具,您可以使其具有某种声明性。这是一个:

[理论,自动生成数据]
public void withrelreturnscorectrect结果(
AtomLink sut,
字符串(newRel)
{
AtomLink实际值=sut.WithRel(newRel);
var expected=sut.AsSource().oflikenness()
.With(x=>x.Rel).EqualsWhen(
(s,d)=>object.Equals(newRel,d.Rel));
预期。应相等(实际);
}
在这里,它仍然相对冗长,但您可以轻松地将其重构为一个通用方法,以便每个测试用例都成为一个单一的测试用例


这仍然是一个麻烦,所以即使你用C语言编写大部分代码,你也可以考虑在一个单独的F库中定义你的不可变类型。从C#上看,F#记录看起来像“普通”的不可变类,如上面的
AtomLink
。与其他一些F#类型(如歧视联合)不同,F#记录完全可以从C#中使用。

以下是我通过具体类模拟C#中不可变突变的尝试。一些魔术通过泛型,其中包括类型安全

class Program
{
    static void Main(string[] args)
    {
        var r = new Random();

        // A new class item
        IDataItem item = new DataItem
        {
            A = r.NextDouble(),
            B = r.NextDouble(),
            C = r.NextDouble(),
            D = r.NextDouble()
        };

        // Type hinting here helps with inference
        // The resulting `newItem` is an "immutable" copy of the source item
        IDataItem newItem = item.With((DataItem x) =>
        {
            x.A = 0;
            x.C = 2;
        });

        // This won't even compile because Bonkers doesn't implement IDataItem!
        // No more casting madness and runtime errors!
        IBonkers newItem2 = item.With((Bonkers x) => { /* ... */ });
    }
}

// A generic record interface to support copying, equality, etc...
public interface IRecord<T> : ICloneable,
                              IComparable,
                              IComparable<T>,
                              IEquatable<T>
{
}

// Immutable while abstract
public interface IDataItem : IRecord<IDataItem>
{
    double A { get; }
    double B { get; }
    double C { get; }
    double D { get; }
}

// Mutable while concrete
public class DataItem : IDataItem
{
    public double A { get; set; }
    public double B { get; set; }
    public double C { get; set; }
    public double D { get; set; }

    public object Clone()
    {
        // Obviously you'd want to be more explicit in some cases (internal reference types, etc...)
        return this.MemberwiseClone();
    }

    public int CompareTo(object obj)
    {
        // Boilerplate...
        throw new NotImplementedException();
    }

    public int CompareTo(IDataItem other)
    {
        // Boilerplate...
        throw new NotImplementedException();
    }

    public bool Equals(IDataItem other)
    {
        // Boilerplate...
        throw new NotImplementedException();
    }
}

// Extension method(s) in a static class!
public static class Extensions
{
    // Generic magic helps you accept an interface, but work with a concrete type
    // Note how the concrete type must implement the provided interface! Type safety!
    public static TInterface With<TInterface, TConcrete>(this TInterface item, Action<TConcrete> fn)
        where TInterface : class, ICloneable
        where TConcrete : class, TInterface
    {
        var n = (TInterface)item.Clone() as TConcrete;
        fn(n);
        return n;
    }
}

// A sample interface to show type safety via generics
public interface IBonkers : IRecord<IBonkers> { }

// A sample class to show type safety via generics
public class Bonkers : IBonkers
{
    public object Clone()
    {
        throw new NotImplementedException();
    }

    public int CompareTo(object obj)
    {
        throw new NotImplementedException();
    }

    public int CompareTo(IBonkers other)
    {
        throw new NotImplementedException();
    }

    public bool Equals(IBonkers other)
    {
        throw new NotImplementedException();
    }
}
类程序
{
静态void Main(字符串[]参数)
{
var r=新的随机变量();
//一个新的类项目
IDataItem项=新数据项
{
A=r.NextDouble(),
B=r.NextDouble(),
C=r.NextDouble(),
D=r.NextDouble()
};
//这里的类型暗示有助于推理
//结果“newItem”是源项的“不可变”副本
IDataItem newItem=项目。带有((数据项x)=>
{
x、 A=0;
x、 C=2;
});
//这甚至不会编译,因为Bonkers没有实现IDataItem!
//不再有疯狂施放和运行时错误!
IBonkers newItem2=item.With((Bonkers x)=>{/*…*/});
}
}
//支持复制、相等等的通用记录接口。。。
公共接口IRecord:iClonable,
我可比,
我可比,
充足的
{
}
//抽象而不变
公共接口IDataItem:IRecord
{
双A{get;}
双B{get;}
双C{get;}
双D{get;}
}
//变而实
公共类数据项:IDataItem
{
公共双精度A{get;set;}
公共双B{get;set;}
公共双C{get;set;}
公共双D{get;set;}
公共对象克隆()
{
//显然,在某些情况下(内部引用类型等),您希望更加明确
返回此.MemberwiseClone();
}
公共整数比较(对象对象对象)
{
//样板。。。
抛出新的NotImplementedException();
}
公共内部比较(IDataItem其他)
{
//样板。。。
抛出新的NotImplementedException();
}
公共布尔等于(IDataItem其他)
{
//样板。。。
抛出新的NotImplementedException();
}
}
//静态类中的扩展方法!
公共静态类扩展
{
//泛型魔术帮助您接受接口,但使用具体类型
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.
}
[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);
}
class Program
{
    static void Main(string[] args)
    {
        var r = new Random();

        // A new class item
        IDataItem item = new DataItem
        {
            A = r.NextDouble(),
            B = r.NextDouble(),
            C = r.NextDouble(),
            D = r.NextDouble()
        };

        // Type hinting here helps with inference
        // The resulting `newItem` is an "immutable" copy of the source item
        IDataItem newItem = item.With((DataItem x) =>
        {
            x.A = 0;
            x.C = 2;
        });

        // This won't even compile because Bonkers doesn't implement IDataItem!
        // No more casting madness and runtime errors!
        IBonkers newItem2 = item.With((Bonkers x) => { /* ... */ });
    }
}

// A generic record interface to support copying, equality, etc...
public interface IRecord<T> : ICloneable,
                              IComparable,
                              IComparable<T>,
                              IEquatable<T>
{
}

// Immutable while abstract
public interface IDataItem : IRecord<IDataItem>
{
    double A { get; }
    double B { get; }
    double C { get; }
    double D { get; }
}

// Mutable while concrete
public class DataItem : IDataItem
{
    public double A { get; set; }
    public double B { get; set; }
    public double C { get; set; }
    public double D { get; set; }

    public object Clone()
    {
        // Obviously you'd want to be more explicit in some cases (internal reference types, etc...)
        return this.MemberwiseClone();
    }

    public int CompareTo(object obj)
    {
        // Boilerplate...
        throw new NotImplementedException();
    }

    public int CompareTo(IDataItem other)
    {
        // Boilerplate...
        throw new NotImplementedException();
    }

    public bool Equals(IDataItem other)
    {
        // Boilerplate...
        throw new NotImplementedException();
    }
}

// Extension method(s) in a static class!
public static class Extensions
{
    // Generic magic helps you accept an interface, but work with a concrete type
    // Note how the concrete type must implement the provided interface! Type safety!
    public static TInterface With<TInterface, TConcrete>(this TInterface item, Action<TConcrete> fn)
        where TInterface : class, ICloneable
        where TConcrete : class, TInterface
    {
        var n = (TInterface)item.Clone() as TConcrete;
        fn(n);
        return n;
    }
}

// A sample interface to show type safety via generics
public interface IBonkers : IRecord<IBonkers> { }

// A sample class to show type safety via generics
public class Bonkers : IBonkers
{
    public object Clone()
    {
        throw new NotImplementedException();
    }

    public int CompareTo(object obj)
    {
        throw new NotImplementedException();
    }

    public int CompareTo(IBonkers other)
    {
        throw new NotImplementedException();
    }

    public bool Equals(IBonkers other)
    {
        throw new NotImplementedException();
    }
}