8

假设我有一个提供一些默认构造函数功能的通用抽象类,因此您不必在继承的类中重复代码:

public abstract class Base<TKey, TValue>
{
    private static readonly IDictionary<TKey, Base<TKey, TValue>> Values = new Dictionary<TKey, Base<TKey, TValue>>();

    public TKey Key { get; private set; }
    public TValue Value { get; private set; }

    protected Base(TKey key, TValue value)
    {
        this.Key = key;
        this.Value = value;

        Values.Add(key, this);
    }
}

现在的问题是,当我编写两个继承的类时:

public sealed class One : Base<string, string>
{
    public static readonly One Default = new One("DEF", "Default value");
    public static readonly One Invalid = new One("", "Invalid value");

    private One(string key, string value) : base(key, value) {}
}

public sealed class Two : Base<string, string>
{
    public static readonly Two EU = new Two("EU", "European Union");
    public static readonly Two USA = new Two("", "United States of America");

    private Two(string key, string value) : base(key, value) {}
}

如您所见,这两个确实是类型安全的枚举。它们只提供预定义的值,不能为任何其他目的实例化。

问题

问题是因为这两个继承的类都使用与基类相同的泛型类型,即Base<string, string>. 泛型类和静态字段的工作方式是为每个泛型类型(或多个泛型类型的组合)创建一个新的静态字段。

在我的情况下,string, string这就是为什么只有一个基类静态字段并且它包含 4 个值而不是两个静态字段,每个字段都有 2 个值的原因。

我如何克服这个问题并将它们分开,同时仍然将此功能保留在基类中,这样我就不会重复我的代码?

将字段从静态更改为实例是行不通的,因为我最终会得到 4 个实例,每个实例只包含一个值......

4

5 回答 5

9

这将起作用。我相信它被称为奇怪的重复模板模式,但不要引用我的话

public abstract class Base<TSelf, TKey, TValue>
{
    private static readonly IDictionary<TKey, Base<TSelf, TKey, TValue>> Values = 
        new Dictionary<TKey, Base<TSelf, TKey, TValue>>();

    public TKey Key { get; private set; }
    public TValue Value { get; private set; }

    protected Base(TKey key, TValue value)
    {
        this.Key = key;
        this.Value = value;

        Values.Add(key, this);
    }
}


public sealed class One : Base<One, string, string>
{
    public static readonly One Default = new One("DEF", "Default value");
    public static readonly One Invalid = new One("", "Invalid value");

    private One(string key, string value) : base(key, value) { }
}

public sealed class Two : Base<Two, string, string>
{
    public static readonly Two EU = new Two("EU", "European Union");
    public static readonly Two USA = new Two("", "United States of America");

    private Two(string key, string value) : base(key, value) { }
}

'好奇'的部分是 和 的定义OneTwo允许将自己用作自己的类型参数!

于 2012-06-19T15:30:11.447 回答
6

当然,问题的出现是因为您的两个类使用相同的泛型类型参数共享相同的基类。这意味着它们共享相同的静态成员。

我如何克服这个问题并将它们分开,同时仍然将此功能保留在基类中,这样我就不会重复我的代码?

一种方法是根据当前运行时类型在字典中构建您的字典:

private static readonly IDictionary<Type, IDictionary<TKey, Base<TKey, TValue>>> Values = new Dictionary<Type, IDictionary<TKey, Base<TKey, TValue>>()>;


protected Base(TKey key, TValue value)
{
    this.Key = key;
    this.Value = value;

    IDictionary<TKey, Base<TKey, TValue>> dict;
    if (!Values.TryGetValue(this.GetType(), out dict)
    { 
        dict = new Dictionary<TKey, Base<TKey, TValue>>();
        Values[this.GetType()] = dict;
    }

    dict.Add(key, this);
}

这确实会导致所有查找成为 2 个阶段,因为您必须查找当前类型然后在该字典中查找值,但它根本不依赖于更改您的子类 API。

于 2012-06-19T15:33:36.203 回答
2

这并没有直接回答这个问题,而是提供了一种替代设计,希望能够实现相同的目标。

在我看来,您似乎正在尝试将基类用于不是基本行为的东西,或者至少不是静态行为。

将作为查找列表和提供查找列表引用的关注点分开。

如果您尝试提供已知的查找,我会将查找列表引用与查找项分开(如,不是静态成员):

public static class MyLookups
{
    public static IDictionary<string, string> Acronyms = new Dictionary<string, string>();
    public static IDictionary<string, string> DefaultValues = new Dictionary<string, string>();
}

然后每种类型都可以简单地指向相关的字典,或者命名它并通过反射访问它,或者提供一个 Func 给它:

public class One : Base<string, string>
{
    public One(string key, string value)
        : base(key, value, () => MyLookups.DefaultValues)
    {
    }
}

public class Two : Base<string, string>
{
    public Two(string key, string value)
        : base(key, value, () => MyLookups.Acronyms)
    {
    }
}

public abstract class Base<TKey, TValue>
{
    private IDictionary<TKey, TValue> _dictionaryReference;

    protected Base(TKey key, TValue value, Func<IDictionary<TKey, TValue>> getDictionary) 
    {
        _dictionaryReference = getDictionary();
        _dictionaryReference.Add(key, value);
    }
}

这也将让您将列表本身与列表中的项目分开(也许通过注入)。

另请注意,如果基类实例保留对相同字典的引用,则无需将成员设为静态。

于 2012-06-19T15:43:55.260 回答
0

让你的基地与众不同。

public abstract class Base<T, TKey, TValue> where T : Base<T, TKey, TValue>
{ 
    private static readonly IDictionary<TKey, T> Values = 
        new Dictionary<TKey, T>(); 

    public TKey Key { get; private set; } 
    public TValue Value { get; private set; } 

    protected Base(TKey key, TValue value) 
    { 
        this.Key = key; 
        this.Value = value; 

        Values.Add(key, (T)this); 
    } 
} 

更新:发布后,我注意到已经给出了或多或少相同的答案,但请注意区别:对类型 T 的约束,以及在字典定义中使用 T。这不是万无一失的,但它有帮助。

需要转换为 T 才能将对象添加到字典中。请注意,我使用的是显式转换,而不是as运算符。如果使用不正确,as运算符会在字典中插入一个空值,而强制转换会破坏它(应该如此)。

但另请参阅:http: //blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx

于 2012-06-19T15:32:59.010 回答
0

这是设计使然。静态成员在所有实例中只有一个副本,而基类中的静态成员在其所有子类的所有实例中只有一个副本。

你为什么是静态的?您真的希望 One 的所有实例共享一个通用字典吗?

如果您希望 One 在 One 之间共享,但不与 Two 共享,那么您将需要更改您的设计。要么使其不是静态的(这将导致不共享),要么在每个子类的级别使其成为静态的。

如果您想在它们之间保持 API 相同,请在基类中创建一个抽象方法/属性并在子类中覆盖

于 2012-06-19T15:34:44.860 回答