【问题标题】:Force a child class to pass itself as the Generic parameter to the base class强制子类将自身作为通用参数传递给基类
【发布时间】:2015-01-07 04:50:59
【问题描述】:

我想强制我的子类将自己作为泛型参数传递给父类。

例如:

class BaseClass<T> where T: BaseClass
{
    //FullClassName : Tuple [Save,Update,Delete]
    Dictionary<string,Tuple<delegate,delegate,delegate>> dict = new Dictionary...;
    static BaseClass()
    {
        RegisterType();
    }

    private static void RegisterType()
    {
        Type t  = typeof(T);
        var props = t.GetProperties().Where(/* Read all properties with the SomeCustomAttribute */);
        /* Create the delegates using expression trees and add the final tuple to the dictionary */
    }

    public virtual void Save()
    {
        delegate d = dict[t.GetType().FullName];
        d.Item1(this);
    }
}

class ChildClass : BaseClass<ChildClass>
{
    [SomeCustomAttribute]
    public int SomeID {get;set;}

    [SomeCustomAttribute]
    public string SomeName {get; set;}
}

public class Program
{
    public static void Main(string[] args)
    {
        ChildClass c = new ChildClass();
        c.Save();
    }
}

显然上面的代码不会编译。我会重申:我希望子类将自身作为通用参数传递,而不是 BaseClass 的任何其他子类。

(上面的代码是一种伪代码,仍然无法编译)。

【问题讨论】:

  • afaik:你不能强求。但你不必。您可以简单地调用GetType 来获取实例的运行时类型。
  • 如果有人能够了解我想要完成的工作,他们能否提供替代方案。主要问题是为每个子类自动执行一次性静态方法。我认为静态构造函数只会对基类执行一次,而不是对每个子类执行一次。
  • 现在看到...考虑一下,如果简单地反映所有类型并注册派生类是否可行。
  • 我认为一个潜在的问题可能是类型存在于多个程序集中,我不确定如何以及何时加载每个程序集。我的代码应该只执行一次,这样就没有不必要的处理。
  • 很明显这是一个 XY 问题。

标签: c# generics


【解决方案1】:

你可以这样做:

public class BaseClass<T> where T: BaseClass<T> { }

public class ChildClass : BaseClass<ChildClass> { }

但这并不强制您使用ChildClass 作为通用参数。你可以这样做public class OtherChildClass : BaseClass&lt;ChildClass&gt; { },这会破坏你想要执行的“合同”。

【讨论】:

    【解决方案2】:

    直接的答案是,如果您访问的是静态方法,那么 typeof(T) 将为您提供反射类型。

    但是,可能有比使用反射更好的解决方案。选项:

    1) 子类的静态构造函数。

    2) 在基类中声明的抽象方法。

    我不了解应用程序,但如果我想使用静态构造函数,我会担心我的设计,我也会担心基类是否需要初始化子类。

    我建议将注入视为一种解决方案,而不是继承。它提供了出色的单元测试,通常还有更好的架构。

    更多信息(在初始帖子之后),这是我的首选解决方案:

    public interface IRegesterable
    {
        void Register();
    }
    
    public class Widget : IRegesterable
    {
        public void Register()
        {
            // do stuff
        }
    }
    
    public class Class1
    {
        public Class1(IRegesterable widget)
        {
            widget.Register();
        }
    }
    

    希望对你有帮助

    【讨论】:

    • 我不会使用反射来创建孩子的实例。我们有一个框架,需要实现一些冗余代码,并且每个开发人员都需要一个学习曲线。代码不能移动到基类,因为它涉及到 xml 的读取和写入。我希望他们创建数据模型类并使用属性标记它们的属性,并且我将使用反射和表达式树来创建代理,这些代理将为他们完成所有冗余工作,而他们甚至不知道幕后发生了什么。
    • 更新后的解决方案的问题在于,反射代码甚至只是检查是否已执行反射的代码都需要每个创建的对象执行。另外,我不希望开发人员这样做任何事情,我什至可以编写代码来生成类并让他们测试它。我想消除人为错误。
    • 查看我的更新回复。接口和注入?我经常发现继承使我的代码更难维护,当然更难测试。
    • 我不建议反思。改用接口和注入。编译时验证并避免混淆(更易读的代码)。
    • Rob,反射代码在对象的生命周期内每个类只执行一次。委托将使用 ExpressionTress 生成,这意味着更少的处理,运行时方法生成。请阅读我的第一条评论。我不会创建类的实例,我将创建一些秘密代码,这些代码将执行开发人员为保存、删除等而编写的冗余任务。他们负责创建类对象,他们将决定他们想要哪些对象创建以及何时创建它们。
    【解决方案3】:

    ConcurrentDictionary 被用作Set&lt;Type&gt;。如果类型已经初始化,我们可以检查Set&lt;Type&gt;。如果不是,我们在类型上运行 RegisterType

    public abstract class BaseClass
    {
        //Concurrent Set does not exist.
        private static ConcurrentDictionary<Type, bool> _registeredTypes 
                = new ConcurrentDictionary<Type, bool>();
    
        protected BaseClass()
        {
            _registeredTypes.GetOrAdd(GetType(), RegisterType);
        }
    
        private static bool RegisterType(Type type)
        {
            //some code that will perform one time processing using reflections
    
            //dummy return value
            return true;
        }
    }
    
    public class ChildClass : BaseClass
    {
    }
    

    不过,这种模式有几个效率低下的地方。

    • object.GetType() 非常缓慢,而且效率低下。
    • 即使使用HashSet 行为,我们也会检查每个实例的初始化。它尽我所能,但它仍然是多余的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-08
      • 2013-10-15
      • 1970-01-01
      • 1970-01-01
      • 2018-05-04
      • 1970-01-01
      • 1970-01-01
      • 2020-03-06
      相关资源
      最近更新 更多