【问题标题】:Is letting a class pass itself as a parameter to a generic base class evil?让一个类将自己作为参数传递给通用基类是邪恶的吗?
【发布时间】:2013-10-15 07:56:27
【问题描述】:

我第一次看到一位同事在实现对象池时这样做。他将要合并的类作为参数传递给通用基类。这个基类对池化代码进行了布局。

奇怪的是基类会知道它的子类。在每个正常情况下,这都被认为是不好的做法。但是在这种情况下,父级只是避免编写重复代码的技术解决方案。任何其他代码都不会引用基类。

这种结构的一个缺点是它“烧毁了基类”。您不能在层次结构的中间引入通用基类。这个问题可能不在主题范围内。

下面是一个可以思考的例子:

public abstract class Singleton<T> where T : class
{
    public static T Instance { get; private set; }

    public Singleton()
    {
        if (Instance != null)
            throw new Exception("Singleton instance already created.");
        Instance = (T) (object) this;
    }
}

public class MyClass : Singleton<MyClass>
{
}

改进的代码:

public abstract class Singleton<T> where T : Singleton<T>
{
    public static T Instance { get; private set; }

    public Singleton()
    {
        if (Instance != null)
            throw new Exception("Singleton instance already created.");
        Instance = (T) this;
    }
}

public class MyClass : Singleton<MyClass>
{
}

【问题讨论】:

    标签: c# oop generics design-patterns


    【解决方案1】:

    没有;这是一个众所周知的模式,称为CRTP
    它在 C++ 中作为虚拟方法的替代品特别有用。

    您可以在IComparable&lt;T&gt;IEquatable&lt;T&gt; 的.Net 框架内看到它。

    为了增加健壮性,您应该添加where T : Singleton&lt;T&gt;

    【讨论】:

    • 你最后一句话让我大吃一惊。类 SingleTon 其中 T: Singleton ?那不是要求你无休止地递归吗(每个 T 都必须是一个 Singleton,而那个 T 又是一个 singleton 等等)
    • 添加了 SLaks 代码改进。第一行确实让你头晕目眩。
    • @Kristof 请参阅上面的示例:public MyClass: Singleton&lt;MyClass&gt;。类型,MyClass IS-A Singleton&lt;MyClass&gt; 所以它工作。不过还是有点脑残。
    • 在对您的示例进行几次处理之后,它确实加起来了 :) 仍然部分脑筋急转弯,但是是的,它是正确的 :)
    • 另见以下讨论,该讨论断言它与 C++ CRTP 不同:stackoverflow.com/questions/16142620/…
    【解决方案2】:

    SLaks 是正确的 - 这是一个有用的模式,通常用于当您想在基类中提供对派生类具有强类型的代码时。

    您通常还会向泛型参数添加类型约束,以指示泛型类型必须从抽象类型继承。添加此约束的语法看起来是递归的,但不要对此感到恐慌 - 它不是递归评估的,只是确保唯一有效的泛型类型是派生类。

    例如,假设您经营茶和咖啡混合业务。将咖啡与咖啡、茶与茶混合对您来说是有意义的,但您要确保不能将咖啡与茶混合。但是,由于它们都是饮料,因此您希望以相同的方式对其进行建模。

    public abstract class Beverage<T> where T : Beverage<T>
    {
        public abstract T Blend(T drink1, T drink2);
    }
    
    public class Tea : Beverage<Tea>
    {
        public override Tea Blend(Tea drink1, Tea drink2)
        { 
            // Blend tea here.
        }
    }
    public class Coffee : Beverage<Coffee>
    {
        public override Coffee Blend(Coffee drink1, Coffee drink2)
        { 
            // Blend coffee here.  Although coffee is nasty, so
            // why you'd want to is beyond me.
        }
    }
    

    在阅读 CRTP 时,值得记住的是,C++ 模板只是表面上类似于 C# 泛型。主要区别在于模板实际上是一种在编译时工作的代码生成工具,而 C# 泛型在运行时受支持。

    此外,编写这样的代码会降低可读性。因此,尽管在某些情况下这肯定是正确的方法,但您应该考虑一下您要解决的问题,看看是否有更直接的方法。

    【讨论】:

    • MyClass.Instance 会编译。
    • 当然,public class Coffee : Beverage&lt;Tea&gt; 是合法声明。这很容易搞砸。
    • 是的,尽管当您尝试使用 Blend() 方法并意识到它期待 Tea 类型时,它会很明显。不过,你是对的 - 很容易把事情搞砸。它也不容易阅读。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-14
    • 2012-10-18
    • 2023-01-18
    • 2023-01-28
    • 2013-01-08
    • 2020-03-14
    • 2013-10-13
    相关资源
    最近更新 更多