【问题标题】:C# Immutable class sub classC#不可变类子类
【发布时间】:2015-02-04 16:49:09
【问题描述】:

我有一个不可变类型,我想创建一个可以访问所有相同方法的子类。

但是,由于您必须实现不可变类,基类方法返回我的父类型,而不是我的子类型。是否可以创建一个不可变的类,该类可以具有返回子类的子类?

下面是在 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

我的不可变

InvalidCastException:无法将“MyImmutable”类型的对象转换为“SubImmutable”类型。

有没有一种方法可以让我拥有一个继承的不可变类型,它可以继承基类型中的所有方法,而不必重新实现它们?

Companion CodeReview 问题:https://codereview.stackexchange.com/questions/79380/inheriting-methods-of-an-immutable-type

【问题讨论】:

  • 你应该看看微软的ImmutableCollections
  • 你可以使用new的方法隐藏。但根据我的经验,继承和不变性并不能很好地融合在一起。您宁愿将只读接口与密封的具体类一起使用。
  • @CodesInChaos 使用不可变类型的全部优势在于,您实际上可以依赖永不改变的类型。如果有允许类型发生变异的方法,即使它们被隐藏,这些假设最终也会被违反。
  • @Greg 我在实际实现中使用了 ImmutableDictionary,这是一个示例。

标签: c# inheritance immutability


【解决方案1】:

您可以使用虚拟方法获取新实例。

在基类中创建一个虚方法,该方法接受输入以创建基类的新实例并返回基类的新实例。然后在子类中覆盖它以生成子类需要的任何其他输入并返回子类的新实例。

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

这样,任何不关心子类的额外内容的转换都不需要在子类中被覆盖。

某些转换可能仍需要被覆盖。例如:

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

如果您希望test1test2 具有相同的SomeOtherValue,则必须将Apply 方法设为虚拟,然后在子类中覆盖它。

【讨论】:

  • 所以我必须重写每个方法?
  • @DLeh 不,让您的所有转换都使用虚拟 GetNew。然后你只需要在子类中覆盖 GetNew 。请注意,这假设子类不需要额外的信息来完成转换。您的示例只是想携带额外的信息,这种方法应该可以正常工作。
  • 哦,我现在明白了。凉爽的!我会试试这个并回复你。
【解决方案2】:

结合不变性和继承的主要问题之一是您希望像 Apply 这样的操作接受和返回调用它的派生类的实例,而不是基类

那就是你希望MyImmutable.Apply 成为:
public MyImmutable Apply(Func&lt;decimal, decimal, decimal&gt; aggFunc, MyImmutable y)

SubImmutable.Apply 是:
public SubImmutable Apply(Func&lt;decimal, decimal, decimal&gt; aggFunc, SubImmutable y)

您可以通过创建一个抽象基类来巧妙地解决这个问题,所有具体类(MyImmutable 和 SubImmutable)都从使用“奇怪的重复模板模式”派生而来

见下文,我还根据自己的喜好更改了您的代码 :) 请注意,此处的 Dict 不是只读的,因此这些类是公开的(并且有效地)不可变但内部可变的。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-03
    • 1970-01-01
    • 2012-07-04
    • 2011-03-08
    • 1970-01-01
    • 2010-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多