我将项目移到使用不可变的类,发现自己做了很多事情:
sealed public class ImmutableClass : IEquatable<ImmutableClass> {
public ImmutableType1 ImmutableType1 { { get; private set; } }
public ImmutableType2 ImmutableType2 { { get; private set; } }
private ImmutableClass(ImmutableType1 immutableType1, immutableType2 ImmutableType2) {
this.ImmutableType1 = immutableType1;
this.ImmutableType2 = immutableType2;
}
public ImmutableClass() {
this.ImmutableType1 = new ImmutableType1();
this.ImmutableType2 = new ImmutableType2();
}
public ImmutableClass Set(ImmutableType1 immutableType1) => new ImmutableClass(immutableType1, ImmutableType2);
public ImmutableClass Set(ImmutableType2 immutableType2) => new ImmutableClass(ImmutableType1, immutableType2);
public override int GetHashCode() => HashCode.Combine(ImmutableType1, ImmutableType2);
public override bool Equals(object obj) {
var o = obj as ImmutableClass; if (o is null) return false;
return ImmutableType1.Equals(o.ImmutableType1) && ImmutableType2.Equals(o.ImmutableType2);
public bool Equals(ImmutableClass o) => object.Equals(this, o);
public static bool operator ==(ImmutableClass o1, ImmutableClass o2) => object.Equals(o1, o2);
public static bool operator !=(ImmutableClass o1, ImmutableClass o2) => !object.Equals(o1, o2);
}
(我在这里强制执行约定,所有字段都是不可变的,所有字段都有一个空的ctor,并且所有字段的类型都不同,Set
才能正常工作)
这很冗长,我做了很多,所以我将其概括如下:
// Implementing the IImutable empty interface denotes the class is immutable, this is then
// used in type constraints in ImmutableBase to ensure members are immutable
public interface IImmutable { };
// Abstract class to facilitate creating immutable classes
public abstract class ImmutableBase<TDerived, T1, T2> : IEquatable<TDerived>, IImmutable
where TDerived : ImmutableBase<TDerived, T1, T2>, new()
where T1 : IImmutable, new()
where T2 : IImmutable, new() {
protected ImmutableBase() { this.item1 = new T1(); this.item2 = new T2(); }
protected T1 item1 { get; private set; }
protected T2 item2 { get; private set; }
public TDerived Set(T1 oitem1) => new TDerived { item1 = oitem1, item2 = item2 };
public TDerived Set(T2 oitem2) => new TDerived { item1 = item1, item2 = oitem2 };
public override int GetHashCode() => HashCode.Combine(item1, item2);
public override bool Equals(object obj) {
var o = obj as TDerived;
return o.IsNull() ? false : item1.Equals(o.item1) && item2.Equals(o.item2);
}
public bool Equals(TDerived o) => object.Equals(this, o);
}
(我再次执行上述约定,并引入了一个不可变接口,所有不可变成员都必须继承该接口,并在约束中使用该接口,而且我使用了一种转换,其中继承ImmutableBase
的类没有任何字段)< / p>
(然后,我为不同数量的参量(从ImmutableBase
到ImmutableBase<TDerived, T1>
等创建了ImmutableBase<TDerived, T1, T2, T3, T4, T5, T6, T7>
的更多版本)
我现在可以做:
#pragma warning disable CS0660, CS0661
sealed public class ImmutableClass :
ImmutableBase<ImmutableClass, ImmutableType1, ImmutableType2> {
public ImmutableClass() { }
public ImmutableType1 ImmutableType1 { get { return item1; } }
public ImmutableType2 ImmutableType2 { get { return item2; } }
public static bool operator ==(ImmutableClass o1, ImmutableClass o2) => object.Equals(o1, o2);
public static bool operator !=(ImmutableClass o1, ImmutableClass o2) => !object.Equals(o1, o2);
}
然后:
ImmutableClass c = new ImmutableClass().Set(new ImmutableType1(1));
ImmutableClass c2 = c.Set(new ImmutableType2("hello"));
var immutableType2 = c2.ImmutableType2;
(需要使用编译指示,因为正确继承了GetHashCode和Equals(object))
但这一切都很好:
问题#1-这种继承如何起作用?我从具有Derived作为参数的泛型类继承,编译器如何解释这一点?这个定义不是递归的吗?
问题#2-如果ImmutableBase
的用户添加字段,这将全部中断,是否可以编写一种可以在运行时确定类本身没有任何字段的属性?
问题#3-并不是一个真正的问题,但是总体上对此想法有何看法?