C# 不可变 class 子 class
C# Immutable class sub class
我有一个不可变类型,我想创建它的一个子class,它可以访问所有相同的方法。
但是,由于您必须如何实现不可变 class,基本 class 方法 return 我的父类型,而不是我的子类型。是否有可能创建一个不可变 class 可以有子 class 是 return 子 class?
下面是 LinqPad 中 运行 的示例代码,演示了该问题
void Main()
{
var immutable = new MyImmutable(new Dictionary<ImmutableKey, decimal>{
{ ImmutableKey.Key1, 1 },
{ ImmutableKey.Key2, -5 },
{ ImmutableKey.Key3, 1.25m },
});
var immutable2 = new MyImmutable(new Dictionary<ImmutableKey, decimal>{
{ ImmutableKey.Key1, 1 },
{ ImmutableKey.Key2, 2 },
{ ImmutableKey.Key3, 3 },
});
var added = immutable.Apply((a, b) => a + b, immutable2);
added[ImmutableKey.Key1].Dump();
added[ImmutableKey.Key2].Dump();
added[ImmutableKey.Key3].Dump();
var subImmutable1 = new SubImmutable(1, new Dictionary<ImmutableKey, decimal>{
{ ImmutableKey.Key1, 1 },
{ ImmutableKey.Key2, -5 },
{ ImmutableKey.Key3, 1.25m },
});
var subImmutable2 = new SubImmutable(1, new Dictionary<ImmutableKey, decimal>{
{ ImmutableKey.Key1, 1 },
{ ImmutableKey.Key2, 2 },
{ ImmutableKey.Key3, 3 },
});
var subImmutableAdded = subImmutable1.Apply((a, b) => a + b, subImmutable2);
subImmutableAdded.GetType().Name.Dump(); //prints MyImmutable, it's not a SubImmutable
//after adding two SubImmutables, the type is changed back to the base type
var asSub = (SubImmutable)subImmutableAdded; // Unable to cast object of type 'MyImmutable' to type 'SubImmutable', SomeOtherValue was lost.
}
public enum ImmutableKey
{
Key1,
Key2,
Key3
}
public class MyImmutable
{
protected static readonly IEnumerable<ImmutableKey> AllKeys = Enum.GetValues(typeof(ImmutableKey)).Cast<ImmutableKey>();
private Dictionary<ImmutableKey, decimal> _dict { get; set; }
public MyImmutable(Dictionary<ImmutableKey,decimal> d)
{
_dict = d;
}
public decimal this[ImmutableKey key]
{
get
{
if (_dict == null || !_dict.ContainsKey(key))
return 0;
return _dict[key];
}
}
public MyImmutable Apply(Func<decimal, decimal, decimal> aggFunc, MyImmutable y)
{
var aggregated = new Dictionary<ImmutableKey, decimal>(AllKeys.Count());
foreach (ImmutableKey bt in AllKeys)
{
aggregated[bt] = aggFunc(this[bt], y[bt]);
}
return new MyImmutable(aggregated);
}
}
public class SubImmutable : MyImmutable
{
public int SomeOtherValue { get; set; }
public SubImmutable(int someValue, Dictionary<ImmutableKey,decimal> d)
:base(d)
{
SomeOtherValue= someValue;
}
}
输出:
2
-3
4.25
MyImmutable
InvalidCastException: Unable to cast object of type 'MyImmutable' to type 'SubImmutable'.
有没有一种方法可以让我拥有一个继承的不可变类型,它可以继承基类型中的所有方法,而不必全部重新实现它们?
配套代码审查问题:https://codereview.stackexchange.com/questions/79380/inheriting-methods-of-an-immutable-type
您可以使用虚拟方法获取新实例。
在基础 class 中创建一个虚方法,该方法接受输入以创建基础的新实例 class 和 return 基础的新实例 class.然后在 subclass 中覆盖它以生成 subclass 需要的任何额外输入和 return subclass.
的新实例
public class MyImmutable
{
// other stuff
// add this method
protected virtual MyImmutable GetNew(Dictionary<ImmutableKey, decimal> d)
{
return new MyImmutable(d);
}
// modify this method as shown
public MyImmutable Apply(Func<decimal, decimal, decimal> aggFunc, MyImmutable y)
{
var aggregated = new Dictionary<ImmutableKey, decimal>(AllKeys.Count());
foreach (ImmutableKey bt in AllKeys)
{
aggregated[bt] = aggFunc(this[bt], y[bt]);
}
return GetNew(aggregated);
}
}
public class SubImmutable : MyImmutable
{
// other stuff
// add this method
protected override MyImmutable GetNew(Dictionary<ImmutableKey, decimal> d)
{
return new SubImmutable(SomeOtherValue, d);
}
}
这样,任何不关心子class额外内容的转换都不需要在子class中被覆盖。
某些转换可能仍需要覆盖。例如:
var one = new SubImmutable(1, alpha);
var two = new SubImmutable(2, alpha);
var test1 = one.Apply((a, b) => a + b, two);
var test2 = two.Apply((a, b) => a + b, one);
Console.WriteLine(test1[someKey] == test2[someKey]); // true
Console.WriteLine(test1.SomeOtherValue == test2.SomeOtherValue); // false
如果您希望 test1
和 test2
具有相同的 SomeOtherValue
,那么您必须将 Apply
方法设为虚拟方法,然后在子class.
结合不可变性和继承的主要问题之一是您希望像 Apply
这样的操作接受并调用派生 class 的 return 实例,而不是基础 class
那是你希望 MyImmutable.Apply
成为:
public MyImmutable Apply(Func<decimal, decimal, decimal> aggFunc, MyImmutable y)
和SubImmutable.Apply
为:
public SubImmutable Apply(Func<decimal, decimal, decimal> aggFunc, SubImmutable y)
您可以通过创建一个抽象基础 class 巧妙地解决这个问题,所有具体的 classes(MyImmutable 和 SubImmutable)都从使用 'curiously recurring template pattern'
派生而来
见下文,我还根据自己的喜好稍微更改了您的代码 :) 请注意,这里的 Dict 不是只读的,因此 classes 是公开的(因此有效地)不可变但在内部是可变的。
public enum ImmutableKey { Key1, Key2, Key3 }
abstract class MyImmutableBase<TDerived> where TDerived : MyImmutableBase<TDerived> {
protected static readonly IEnumerable<ImmutableKey> AllKeys = Enum.GetValues(typeof(ImmutableKey)).Cast<ImmutableKey>();
private ImmutableDictionary<ImmutableKey, decimal> Dict;
public MyImmutableBase() => Dict = ImmutableDictionary<ImmutableKey, decimal>.Empty;
protected abstract TDerived GetNew();
public decimal this[ImmutableKey key] { get { if (Dict == null || !Dict.ContainsKey(key)) return 0; return Dict[key]; } }
public TDerived Add(IEnumerable<KeyValuePair<ImmutableKey, decimal>> d) {
var res = GetNew();
res.Dict = res.Dict.AddRange(d);
return res;
}
public TDerived Apply(Func<decimal, decimal, decimal> aggFunc, TDerived y) {
var aggregated = ImmutableDictionary<ImmutableKey, decimal>.Empty;
foreach (ImmutableKey bt in AllKeys) aggregated = aggregated.SetItem(bt, aggFunc(this[bt], y[bt]));
return GetNew().Add(aggregated);
}
}
class MyImmutable : MyImmutableBase<MyImmutable> {
protected override MyImmutable GetNew() => new();
}
class SubImmutable : MyImmutableBase<SubImmutable> {
public int SomeOtherValue { get; init; }
public SubImmutable(int someValue) : base() => SomeOtherValue = someValue;
protected override SubImmutable GetNew() => new(SomeOtherValue);
}
我有一个不可变类型,我想创建它的一个子class,它可以访问所有相同的方法。
但是,由于您必须如何实现不可变 class,基本 class 方法 return 我的父类型,而不是我的子类型。是否有可能创建一个不可变 class 可以有子 class 是 return 子 class?
下面是 LinqPad 中 运行 的示例代码,演示了该问题
void Main()
{
var immutable = new MyImmutable(new Dictionary<ImmutableKey, decimal>{
{ ImmutableKey.Key1, 1 },
{ ImmutableKey.Key2, -5 },
{ ImmutableKey.Key3, 1.25m },
});
var immutable2 = new MyImmutable(new Dictionary<ImmutableKey, decimal>{
{ ImmutableKey.Key1, 1 },
{ ImmutableKey.Key2, 2 },
{ ImmutableKey.Key3, 3 },
});
var added = immutable.Apply((a, b) => a + b, immutable2);
added[ImmutableKey.Key1].Dump();
added[ImmutableKey.Key2].Dump();
added[ImmutableKey.Key3].Dump();
var subImmutable1 = new SubImmutable(1, new Dictionary<ImmutableKey, decimal>{
{ ImmutableKey.Key1, 1 },
{ ImmutableKey.Key2, -5 },
{ ImmutableKey.Key3, 1.25m },
});
var subImmutable2 = new SubImmutable(1, new Dictionary<ImmutableKey, decimal>{
{ ImmutableKey.Key1, 1 },
{ ImmutableKey.Key2, 2 },
{ ImmutableKey.Key3, 3 },
});
var subImmutableAdded = subImmutable1.Apply((a, b) => a + b, subImmutable2);
subImmutableAdded.GetType().Name.Dump(); //prints MyImmutable, it's not a SubImmutable
//after adding two SubImmutables, the type is changed back to the base type
var asSub = (SubImmutable)subImmutableAdded; // Unable to cast object of type 'MyImmutable' to type 'SubImmutable', SomeOtherValue was lost.
}
public enum ImmutableKey
{
Key1,
Key2,
Key3
}
public class MyImmutable
{
protected static readonly IEnumerable<ImmutableKey> AllKeys = Enum.GetValues(typeof(ImmutableKey)).Cast<ImmutableKey>();
private Dictionary<ImmutableKey, decimal> _dict { get; set; }
public MyImmutable(Dictionary<ImmutableKey,decimal> d)
{
_dict = d;
}
public decimal this[ImmutableKey key]
{
get
{
if (_dict == null || !_dict.ContainsKey(key))
return 0;
return _dict[key];
}
}
public MyImmutable Apply(Func<decimal, decimal, decimal> aggFunc, MyImmutable y)
{
var aggregated = new Dictionary<ImmutableKey, decimal>(AllKeys.Count());
foreach (ImmutableKey bt in AllKeys)
{
aggregated[bt] = aggFunc(this[bt], y[bt]);
}
return new MyImmutable(aggregated);
}
}
public class SubImmutable : MyImmutable
{
public int SomeOtherValue { get; set; }
public SubImmutable(int someValue, Dictionary<ImmutableKey,decimal> d)
:base(d)
{
SomeOtherValue= someValue;
}
}
输出:
2
-3
4.25
MyImmutable
InvalidCastException: Unable to cast object of type 'MyImmutable' to type 'SubImmutable'.
有没有一种方法可以让我拥有一个继承的不可变类型,它可以继承基类型中的所有方法,而不必全部重新实现它们?
配套代码审查问题:https://codereview.stackexchange.com/questions/79380/inheriting-methods-of-an-immutable-type
您可以使用虚拟方法获取新实例。
在基础 class 中创建一个虚方法,该方法接受输入以创建基础的新实例 class 和 return 基础的新实例 class.然后在 subclass 中覆盖它以生成 subclass 需要的任何额外输入和 return subclass.
的新实例public class MyImmutable
{
// other stuff
// add this method
protected virtual MyImmutable GetNew(Dictionary<ImmutableKey, decimal> d)
{
return new MyImmutable(d);
}
// modify this method as shown
public MyImmutable Apply(Func<decimal, decimal, decimal> aggFunc, MyImmutable y)
{
var aggregated = new Dictionary<ImmutableKey, decimal>(AllKeys.Count());
foreach (ImmutableKey bt in AllKeys)
{
aggregated[bt] = aggFunc(this[bt], y[bt]);
}
return GetNew(aggregated);
}
}
public class SubImmutable : MyImmutable
{
// other stuff
// add this method
protected override MyImmutable GetNew(Dictionary<ImmutableKey, decimal> d)
{
return new SubImmutable(SomeOtherValue, d);
}
}
这样,任何不关心子class额外内容的转换都不需要在子class中被覆盖。
某些转换可能仍需要覆盖。例如:
var one = new SubImmutable(1, alpha);
var two = new SubImmutable(2, alpha);
var test1 = one.Apply((a, b) => a + b, two);
var test2 = two.Apply((a, b) => a + b, one);
Console.WriteLine(test1[someKey] == test2[someKey]); // true
Console.WriteLine(test1.SomeOtherValue == test2.SomeOtherValue); // false
如果您希望 test1
和 test2
具有相同的 SomeOtherValue
,那么您必须将 Apply
方法设为虚拟方法,然后在子class.
结合不可变性和继承的主要问题之一是您希望像 Apply
这样的操作接受并调用派生 class 的 return 实例,而不是基础 class
那是你希望 MyImmutable.Apply
成为:public MyImmutable Apply(Func<decimal, decimal, decimal> aggFunc, MyImmutable y)
和SubImmutable.Apply
为:public SubImmutable Apply(Func<decimal, decimal, decimal> aggFunc, SubImmutable y)
您可以通过创建一个抽象基础 class 巧妙地解决这个问题,所有具体的 classes(MyImmutable 和 SubImmutable)都从使用 'curiously recurring template pattern'
派生而来见下文,我还根据自己的喜好稍微更改了您的代码 :) 请注意,这里的 Dict 不是只读的,因此 classes 是公开的(因此有效地)不可变但在内部是可变的。
public enum ImmutableKey { Key1, Key2, Key3 }
abstract class MyImmutableBase<TDerived> where TDerived : MyImmutableBase<TDerived> {
protected static readonly IEnumerable<ImmutableKey> AllKeys = Enum.GetValues(typeof(ImmutableKey)).Cast<ImmutableKey>();
private ImmutableDictionary<ImmutableKey, decimal> Dict;
public MyImmutableBase() => Dict = ImmutableDictionary<ImmutableKey, decimal>.Empty;
protected abstract TDerived GetNew();
public decimal this[ImmutableKey key] { get { if (Dict == null || !Dict.ContainsKey(key)) return 0; return Dict[key]; } }
public TDerived Add(IEnumerable<KeyValuePair<ImmutableKey, decimal>> d) {
var res = GetNew();
res.Dict = res.Dict.AddRange(d);
return res;
}
public TDerived Apply(Func<decimal, decimal, decimal> aggFunc, TDerived y) {
var aggregated = ImmutableDictionary<ImmutableKey, decimal>.Empty;
foreach (ImmutableKey bt in AllKeys) aggregated = aggregated.SetItem(bt, aggFunc(this[bt], y[bt]));
return GetNew().Add(aggregated);
}
}
class MyImmutable : MyImmutableBase<MyImmutable> {
protected override MyImmutable GetNew() => new();
}
class SubImmutable : MyImmutableBase<SubImmutable> {
public int SomeOtherValue { get; init; }
public SubImmutable(int someValue) : base() => SomeOtherValue = someValue;
protected override SubImmutable GetNew() => new(SomeOtherValue);
}