【问题标题】:Overridable methods cannot be static: How else can I do what I'm trying to do?可覆盖的方法不能是静态的:我还能怎么做我想做的事?
【发布时间】:2014-08-25 16:24:07
【问题描述】:

我有一系列静态类,用于获取枚举值的字符串。它们看起来都像这样:

public static class MyEnumToString
{
  private static Dictionary<MyEnum, string> map
   = new Dictionary<MyEnum, string>();

  public static string Get(MyEnum type)
  {
    PopulateEmptyMap();
    return map[type];
  }

  static void PopulateEmptyMap()
  {
    if (!map.Any())
    {
      PopulateMap();
    }
  }

  private static void PopulateMap()
  {
    map[MyEnum.enum1] = "string for enum 1";
    map[MyEnum.enum2] = "string for enum 2";
  }
}

我有多个这样的类,它们使用的 Enum 类型和字符串值不同。显然,我应该结合这些类来减少重复代码。

我尝试做的是创建通用基类,以便它可以处理任何类型,然后为继承的类实现 PopulateMap。如果可能的话,它看起来像这样:

public static class TypeToString<TType>
{
  public static Dictionary<TType, string> map
   = new Dictionary<TType, string>();

  public static string Get(TType type)
  {
    PopulateEmptyMap();
    return map[type];
  }

  static void PopulateEmptyMap()
  {
    if (!map.Any())
    {
      PopulateMap();
    }
  }

  public abstract static void PopulateMap();
}

public static class MyEnumToString: TypeToString<MyEnum>
{
  public static void PopulateMap()
  {
    map[MyEnum.enum1] = "string for enum 1";
    map[MyEnum.enum2] = "string for enum 2";
  }
}

我不得不公开 Dictionary 和 PopulateMap 方法,因为显然泛型类不能有受保护或受保护的内部成员。必须公开这一点并不理想,但也不会破坏交易。

我最关心的是“可覆盖的方法不能是静态的”,所以我的 PopulateMap 方法不能既是抽象的又是静态的。如果它不是静态的,则不能从其他静态方法中调用它。如果它不是抽象的,那么继承类的 PopulateMap 就不会被调用。

这个版本甚至没有构建。

有什么方法可以做我想做的事情并且仍然保持我的班级静态吗?我真的很想避免每次我想调用 TypeToString.Get() 时都必须实例化一个 TypeToString 对象。

【问题讨论】:

  • 提示:为什么不为枚举创建属性创建从属性中提取字符串的扩展方法?
  • 您可以让静态代码在模板本地创建一个非静态类的实例,然后该实例包含您的所有虚函数,并让静态类通过该模板的单个实例路由所有内容处理程序类。您还应该能够参数化重载函数而不是使其成为虚拟函数。即:有MyEnumToString&lt;X&gt; when X: Enum1,一个when X: Eum2,等等

标签: c#


【解决方案1】:

这是一个方便的扩展方法,我猜你正在尝试将一些描述文本映射到一个枚举值:

public static class EnumExtensions
{
    public static string GetDescription(this Enum value)
    {
        var field = value.GetType().GetField(value.ToString());
        if (field == null)
            return value.ToString();

        var attribute = field.GetCustomAttributes(typeof(DescriptionAttribute), false)
                             .OfType<DescriptionAttribute>()
                             .SingleOrDefault();

        return attribute != null
            ? attribute.Description
            : value.ToString();
    }
}

像这样使用它:

public enum Foo
{
    [Description("Hello")]
    Bar,

    [Description("World")]
    Baz
}

var value = Foo.Bar;
var description = value.GetDescription(); // Hello

根据您的需要,如果反射证明对您来说太慢,您可以缓存描述,只需修改GetDescription 方法。


编辑:说明评论中的附加信息。

看起来你需要一些更可扩展的东西,你可以使用自定义属性:

[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public sealed class DescriptionEntryAttribute : Attribute
{
    public string Key { get; private set; }
    public string Value { get; private set; }

    public DescriptionEntryAttribute(string key, string value)
    {
        Key = key;
        Value = value;
    }
}

这会让你这样做:

public enum Foo
{
    [DescriptionEntry("Name", "Hello")]
    [DescriptionEntry("Title", "Some title")]
    Bar,

    [DescriptionEntry("Name", "World")]
    [DescriptionEntry("Title", "Some title")]
    Baz
}

现在,要阅读此内容,我建议您将其存储在这样的缓存中:

public static class EnumExtensions
{
    private static readonly ConcurrentDictionary<Type, DescriptionCache> Caches = new ConcurrentDictionary<Type, DescriptionCache>();

    public static string GetDescription(this Enum value, string key)
    {
        var enumType = value.GetType();
        var cache = Caches.GetOrAdd(enumType, type => new DescriptionCache(type));
        return cache.GetDescription(value, key);
    }

    public static IEnumerable<TEnum> GetValuesFromDescription<TEnum>(string key, string description)
        where TEnum : struct
    {
        var cache = Caches.GetOrAdd(typeof(TEnum), type => new DescriptionCache(type));
        return cache.GetValues(key, description).Select(value => (TEnum)(object)value);
    }

    private class DescriptionCache
    {
        private readonly ILookup<Enum, Tuple<string, string>> _items;
        private readonly ILookup<Tuple<string, string>, Enum> _reverse;

        public DescriptionCache(Type enumType)
        {
            if (!enumType.IsEnum)
                throw new ArgumentException("Not an enum");

            _items = (from value in Enum.GetValues(enumType).Cast<Enum>()
                      let field = enumType.GetField(value.ToString())
                      where field != null
                      from attribute in field.GetCustomAttributes(typeof (DescriptionEntryAttribute), false).OfType<DescriptionEntryAttribute>()
                      select new {value, key = attribute.Key, description = attribute.Value})
                .ToLookup(i => i.value, i => Tuple.Create(i.key, i.description));

            _reverse = (from grp in _items
                        from description in grp
                        select new {value = grp.Key, description})
                .ToLookup(i => i.description, i => i.value);
        }

        public string GetDescription(Enum value, string key)
        {
            var tuple = _items[value].FirstOrDefault(i => i.Item1 == key);
            return tuple != null ? tuple.Item2 : null;
        }

        public IEnumerable<Enum> GetValues(string key, string description)
        {
            return _reverse[Tuple.Create(key, description)];
        }
    }
}

这边:

  • Foo.Bar.GetDescription("Name") 返回"Hello"
  • EnumExtensions.GetValuesFromDescription&lt;Foo&gt;("Title", "Some title") 返回一个包含 Foo.Bar 和 Foo.Baz 的序列

这应该足以让您入门,现在您应该根据自己的需要对其进行调整。例如,您可以使用枚举而不是字符串作为键,这将有助于避免输入错误,但我不知道这是否符合您的需要。

【讨论】:

  • 关于您的回复的几个问题: 1. 如何缓存 GetDescription 方法中的描述? 2. 这可能适用于多种字符串类型吗?虽然我没有在 OP 中描述这一点,但我有多个类和单个枚举的字典,为同一个枚举提供不同类型的字符串? 3. 是否可以反过来执行此操作,以便给定一个字符串,您可以查询它与哪个枚举一起使用?同样,这不在最初的问题中,但我的一些课程的字典是相反的。
  • @PortMan 好的,我已经做了一个更详细的例子
【解决方案2】:

您的问题是静态方法和变量本质上不是继承的。它们是不作用于类本身的实例的变量,但为类提供一些功能。

所以你有一堆不同的枚举,你想根据不同的东西来填充它们。那么让我们看看你有哪些部分,以及哪些部分是常见的:

  • PopulateMap:不常见
  • 枚举类型:不常见
  • 存储变量:通用
  • 如果为空则填充地图:通用

因此,您真正想要的只是一种在使用地图时一次性填充地图的方法。已经有一个类,它叫做Lazy。使用它,代码变为:

public abstract class TypeToString<Type>
{
    protected TypeToString()
    {
        storage = new Lazy<Dictionary<Type, string>>(GetMap);
    }
    private Lazy<Dictionary<Type, string>> storage;
    protected abstract Dictionary<Type, string> GetMap();
    public string Get(Type t) {return storage.Value[t];}
}
public class MyEnumToString : TypeToString<MyEnum>
{
    protected override Dictionary<MyEnum, string> GetMap()
    {
        return null;
    }
    public static Get(MyEnum e) { return new MyEnumToString.Get(e); }
}

或者,您可以使用[DescriptionAttribute] 装饰您的枚举,然后创建一个方法来获取特定枚举的描述。这就是我在遇到类似问题时所做的。 (一定要缓存枚举的结果,因为它使用了很慢的反射。)

【讨论】:

  • 因为它不是静态的,我必须有一个实例化的 MyEnumToString 对象才能调用 Get() 方法,对吗?这就是我试图找到解决方法的问题:如何在不必实例化对象的情况下做到这一点。我绝对看到你的解决方案比我的更有价值——既然轮子已经在那里等待我使用,为什么还要发明自己的轮子?
  • @Portman 同样,如果您只想将多个枚举转换为字符串,并且您提前知道这些值,我会选择使用 [DescriptionAttribute] 并检查这些值的解决方案.另一个问题是为什么你不想实例化对象?物件很便宜。除非您有问题,否则不要担心对象实例化。
  • 我不确定 [DescriptionAttribute] 是否适合我,因为 a) 我需要能够将枚举转换为不止一种类型的字符串(我目前有多个枚举->单个枚举的字符串类),并且b)我需要能够做相反的事情,转换字符串->枚举。至于我为什么不想实例化对象,纯粹是为了避免在实例化对象的代码中多出一行。我是 C# 新手,我想我正试图让 C# 更像是一种函数式语言而不是 OO 语言。
  • @Portman 那么你有几个选择。 1)向每个获取该类型的值并处理实例化的类添加一个静态方法(我已经用它更新了我的答案)。 2) 为每种类型的转换“GetStringForAccount(MyEnum e)”创建一个具有不同方法的类。 3)创建扩展方法来转换枚举。考虑到您的额外限制,这是我将采取的方法。
猜你喜欢
  • 1970-01-01
  • 2011-02-27
  • 1970-01-01
  • 2016-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-27
相关资源
最近更新 更多