【问题标题】:Creating an instance of derived class through the base class without hardcoding通过基类创建派生类的实例,无需硬编码
【发布时间】:2013-10-30 19:39:58
【问题描述】:

我的问题如下:

我有一个需要抽象的基类。它有几个派生类,每个派生类都有自己的特殊属性,这些属性包含在 Properties 成员中。

我需要能够创建这些派生类之一的新实例,以便所有成员都是等效的,但修改新实例不会修改原始实例。

最后,我想这样做,而不必在基类的每个派生类型中进行硬编码。 (诚​​然,这是最简单的解决方案,但这不是重点)

所有派生类都满足与基类的“is-a”关系。

代码如下:

public abstract class BaseClass
{
    //Default properties here
    int x, y, z, ...;

    //Custom made class to hold custom properties
    protected Attributes Properties;

    public BaseClass createNewInstance()
    {
        return createNewInstanceStep1();
    }

    //Each derived class implements their own version of this,
    //to handle copying any custom members contained in Properties.
    protected abstract BaseClass createNewInstanceStep2();

    protected BaseClass createNewInstanceStep1()
    {
        BaseClass newInstance = new BaseClass(); // <- Doesn't work because class is abstract

        //Copy default properties
        newInstance.x = x;
        newInstance.y = y;
        newInstance.z = z;

        //Call the new instance's step 2 method, and return the result.
        return newInstance.createNewInstanceStep2();
    }
}

此代码的问题在于 BaseClass newKeyFrame = new BaseClass();线。由于该类是抽象的,因此您不能创建它的实例。

问题是我需要能够调用派生类的任何类型的构造函数,因为它们的构造函数中都有不同的代码,无法共享。

我听说使用反射可能是一个可行的解决方案,但我不知道如何。

我怎样才能解决这个问题,而不必在每个派生类型的案例中进行硬编码?

【问题讨论】:

    标签: c# inheritance constructor abstract-class


    【解决方案1】:

    您可以将createNewInstanceStep1 设为通用。我还将Step2 修改为void 类型(我希望它修改当前实例,所以无论如何返回总是return this;),因为否则它并没有真正有意义的方式我想在这里使用它。如果像这样改变它没有意义,那么我只使这个方法通用的整个方法就行不通了。

    createNewInstance 现在使用反射来调用return createNewInstanceStep1&lt;this.GetType()&gt;(); 的等价物。

    public BaseClass createNewInstance()
    {
        var method = typeof(BaseClass).GetMethod("createNewInstanceStep1", BindingFlags.NonPublic | BindingFlags.Instance).MakeGenericMethod(this.GetType());
        var value = method.Invoke(this, null);
        return (BaseClass)value;
    }
    
    //Each derived class implements their own version of this,
    //to handle copying any custom members contained in Properties.
    protected abstract void createNewInstanceStep2();
    
    protected T createNewInstanceStep1<T>() where T : BaseClass, new()
    {
        T newInstance = new T(); // works!
    
        //Copy default properties
        newInstance.x = x;
        newInstance.y = y;
        newInstance.z = z;
    
        //Call the new instance's step 2 method, and return the result.
        newInstance.createNewInstanceStep2();
        return newInstance;
    }
    

    如果这不起作用,另一种方法是self-referential generic type。不过最好避免这种情况,因为它会造成混淆,而且总体上不是一个好的设计。

    public sealed class SubClass : BaseClass<SubClass>
    {
        protected override SubClass createNewInstanceStep2()
        {
            Console.WriteLine("In step 2");
            return this;
        }
    }
    public abstract class BaseClass<T> where T : BaseClass<T>, new()
        public T createNewInstance()
        {
            return createNewInstanceStep1();
        }
    
        //Each derived class implements their own version of this,
        //to handle copying any custom members contained in Properties.
        protected abstract T createNewInstanceStep2();
    
        protected T createNewInstanceStep1()
        {
            T newInstance = new T();
            ...
    

    【讨论】:

    • 工作得非常好。请问where T : BaseClass, new()在做什么?
    • 这是两个constraintsT 必须是BaseClassBaseClass 或扩展它的东西),并且它必须公开一个无参数的构造函数。这确保您可以使用 new T() 并像使用 BaseClass 一样使用 newInstance
    【解决方案2】:

    让派生类实例化新实例有什么问题?

    public abstract class BaseClass
    {
        //Default properties here
        int x, y, z, ...;
    
        //Custom made class to hold custom properties
        protected Attributes Properties;
    
        public abstract BaseClass createNewInstance();
    
        protected void CopyData(BaseClass original)
        {
            this.x = original.x;
            this.y = original.y;
            this.z = original.z;
        }
    }
    
    public class ChildClass : BaseClass
    {
        public BaseClass createNewInstance()
        {
            ChildClass newInstance = new ChildClass();
            newInstance.CopyData(this);
    
            // Do any additional copying
            return newInstance;
        }
    }
    

    【讨论】:

    • 如果我这样做,那么我必须重写复制所有共享属性。虽然不多,但仍然没有我希望的那么高效。
    • 不,你没有。如您所见,共享属性的复制是在 BaseClass.CopyData 中处理的。您只需要确保从每个 ChildClass.createNewInstance 调用 CopyData
    • 误读代码,哎呀。是的,这会很好用。它更简单并且符合相同的目的。 (尽管对代码进行了一些修改)+1
    【解决方案3】:

    您可以将 createNewInstanceStep1 设为一个受保护的 void init 方法来启动该对象,并在每个子类的构造函数中调用它。

    【讨论】:

      【解决方案4】:

      您如何知道要创建什么类型的实例?如果您将拥有派生类的现有实例并希望创建另一个与其基本相同的实例,您应该让您的基类型实现一个受保护的虚拟 CloneBase 方法,该方法调用 MemberwiseClone 并进行任何深度复制基本类型知道,还有一个公共的Clone 方法链接到CloneBase 并转换为基本类型。每个派生类型都应该覆盖 CloneBase 以链接到 base.CloneBase 并添加任何必要的额外深度复制(如果不需要额外的深度复制逻辑,则可以省略该步骤),并使用链接到CloneBase 并将结果转换为其类型[使用单独的CloneBase 可以声明具有不同签名的新Clone 方法,同时还可以覆盖和链接到基类方法]。

      如果您将拥有新类的现有实例,但希望从某个其他实例复制其属性,则可以有一个抽象的 ConstructInstanceLike(x) 方法,每个派生类型都将实现该方法要么调用其构造函数之一,要么克隆自身并修改克隆以匹配传入的对象。这两种方法都不是非常优雅,但都可以工作。

      如果您没有新类的现有实例,则需要一些其他方法来获取适当类型的内容。最好的方法可能是存储Func&lt;TParams, TResult&gt; 委托的集合,一个代表每个感兴趣的派生类型,然后调用其中一个函数来生成相关派生类型之一的对象。也可以定义一个接口

      IFactory<TParam, TResult> { TResult Create(TParams param); }
      

      但在许多情况下,使用代理会更方便。

      【讨论】:

      • 你要求我完全按照我所说的我不想想要做的事情,这是每个派生类型中的硬编码。
      • @lpquarx:基于克隆的方法不需要在不添加需要特殊工作的新成员的各个派生类中进行硬编码。如果您将基于函数的方法与 lambdas 一起使用,您可能会非常好地创建一个表,其中每个支持的类型都有一行源代码(类似于AddFactory&lt;FooBar&gt;((x) =&gt; new FooBar(x));)。这对你的口味来说是不是太多“硬编码”了?
      • 这显然并不太难,但正如问题所述,目标是不需要任何硬编码,而接受的答案正是为我提供了这一点。 (虽然我承认这会很好)
      • 接受的答案要求派生类具有公共无参数构造函数。如果愿意接受这个条件,就不必使用反射来创建实例;可以创建一个新实例,然后在该实例上使用CopyFrom 方法(它可以从基类继承)来复制其成员。使用MemberwiseClone 的方法可以避免需要new 约束,但必须小心使克隆对象中不应出现在新实例中的任何数据无效。
      猜你喜欢
      • 2020-10-28
      • 2014-09-29
      • 1970-01-01
      • 2023-02-22
      • 2011-11-05
      • 1970-01-01
      • 2013-11-16
      • 2021-02-28
      • 2014-04-27
      相关资源
      最近更新 更多