【问题标题】:Derived class instantiation in abstract base class in C#C#抽象基类中的派生类实例化
【发布时间】:2015-03-23 16:48:40
【问题描述】:

我的目标是编写一个包含派生“子实例”的方法的抽象基类。在这种方法中,已经完成了一些计算,这在所有派生类中都很常见。

困难在于基类不能自己创建子类。所以我在我的基类中引入了一个类型参数T 和一个protected abstract 方法,它应该返回一个T 的实例。

public abstract class Base<T> where T : Base<T>
{
    public T GetChild()
    {
        string param = ComplexComputation();
        return NewInstanceFrom(param);
    }

    protected abstract T NewInstanceFrom(string param);
}

// --- somewhere else: ---

public class Derivative : Base<Derivative>
{
    public Derivative() { }

    protected sealed override Derivative NewInstanceFrom(string param)
    {
        return new Derivative(param);
    }

    private Derivative(string param)
    {
        // some configuration
    }
}

这种方法的缺点是我无法确保NewInstanceFrom 仅由基类调用。它也可以被继承自Derivative 的类调用。这就是我想要避免的。

所以我可以将功能封装在私有类或委托中:

public abstract class Base<T> where T : Base<T>
{
    public T GetChild()
    {
        string param = ComplexComputation();
        return subElementDerivator(param);
    }

    protected Base<T>(Func<string, T> subElementDerivator)
    {
        this.subElementDerivator = subElementDerivator;
    }

    private Func<string, T> subElementDerivator;
}

// --- somewhere else: ---

public class Derivative : Base<Derivative>
{
    public Derivative()
        : base(deriveSubElement)    
    {
    }

    private Derivative(string param)
        : base(deriveSubElement)
    {
        // some configuration
    }

    private static Derivative deriveSubElement(string param)
    {
        return new Derivative(param);
    }
}

但这引入了一个新对象。

是否有更简单的方法来阻止Derivative 的继承者访问功能(基类应有权访问)?

【问题讨论】:

  • 你的第二个代码示例是我能想到的完成你想要的唯一方法。
  • C# 的弱点是创建通用工厂几乎是不可能的。以上可以通过发出我认为的操作码来完成。
  • 派生类调用该方法到底有什么问题?是怕自己误调用,还是怕第三方派生类不经过你的同意就调用?
  • @Jon 你带来了很好的理由。我正在开发一个框架,我想清除那些没有实用性或在当前上下文中颠覆概念但可以调用的方法。
  • 如果是这种情况,那么设置internal 可见性将解决您的问题。这就是大多数 .NET 框架为其“特殊”构造函数处理相同问题的方式。

标签: c# inheritance


【解决方案1】:

您可以使用显式接口实现来隐藏您的工厂方法。任何客户端在转换后仍然可以调用Create 方法,但至少智能感知不会帮助开发人员。

public interface ISecretFactory<T>
{
    T Create(string param);
}

public abstract class Base<T> where T : Base<T>, ISecretFactory<T>
{
    public T GetChild()
    {
        // We are sure type T always implements ISecretFactory<T>
        var factory = this as ISecretFactory<T>;
        return factory.Create("base param");
    }
}

public class Derivative : Base<Derivative>, ISecretFactory<Derivative>
{
    public Derivative()
    {

    }

    private Derivative(string param)
    {

    }

    Derivative ISecretFactory<Derivative>.Create(string param)
    {
        return new Derivative(param);
    }
}

public class SecondDerivative : Derivative
{
    public void F()
    {
        // intellisense won't show Create method here.
        // But 'this as ISecretFactory<Derivative>' trick still works.
    }
}

【讨论】:

  • 总是可以调用任何你可以从其他地方调用的东西,所以这是一个相当不错的解决方案。当然,它还是有点味道,但如果你真的想要这样的工厂,这也不算太糟糕。
【解决方案2】:

可以通过将ComplexComputation 移至基类的构造函数并使GetChild 方法抽象来让派生类在那里选择正确的构造函数来避免额外的对象。

但是如何将基础构造函数中的计算值param 返回给调用派生构造函数?一种可能是使用out 参数修饰符。但是因为在 C# 5.0 中我们很遗憾无法在基构造函数调用之前(或之内)声明变量,所以我们需要在派生构造函数中带上参数。

public abstract class Base<T> where T : Base<T>
{
    public abstract T GetChild();

    protected Base(T parent, out string param)
    {
        param = ComplexComputation();
    }

    protected Base()
    {
    }
}

// --- somewhere else: ---

public class Derivative : Base<Derivative>
{
    public sealed override Derivative GetChild()
    {
        string param;
        return new Derivative(this, out param);
    }

    public Derivative() { }

    private Derivative(Derivative parent, out string param)
        : base(parent, out param)
    {
        // some configuration
    }
}

在我的情况下,我可以将 param 远离构造函数,而是将其存储在 public 属性中。

除了讨厌的必要 hack 之外,这种方法在我看来相对干净,但当需要多次重载 GetChild 时,它不会“扩展”。

也许在 C# 6.0 中,可以直接在基础构造函数调用中声明 paramhttps://msdn.microsoft.com/de-de/magazine/dn683793.aspx

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-28
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 2021-08-24
    • 2021-07-24
    • 1970-01-01
    • 2018-06-26
    相关资源
    最近更新 更多