【问题标题】:Is it a bad idea to use Generic Types to 'switch on types'? [closed]使用泛型类型“打开类型”是一个坏主意吗? [关闭]
【发布时间】:2013-08-27 13:10:03
【问题描述】:

C# - Is there a better alternative than this to 'switch on type'?相关

我需要“开启类型”:给定一个T 类型的参数,查找并执行void Method (T param) 形式的方法。

这可以通过 switch 语句或Dictionary<Type, Action> 来解决,但是我想避免在这种情况下进行必要的强制转换。

我找不到上述(或类似)问题中提到的以下方法:

  1. 创建一个充当集合的静态泛型类型:

    public static class Commands<T> {
      public static Action<T> Handler;
    }
    
  2. 创建一个像类型安全字典一样使用该类型的存储库:

    public class CommandRepository {
        public void Register<T>(Action<T> handler) {
            Commands<T>.Handler = handler;
        }
    
        public void Run<T>(T parameter) {
            // null checks etc.
            Commands<T>.Handler(parameter);
        }
    }
    
  3. 示例用法:

    public void CreateUser(CreateUserParams p) {
        Console.WriteLine("Creating " + p.Name);
    }
    
    // ...
    var repo = new CommandRepository();
    
    repo.Register<CreateUserParams>(CreateUser);
    repo.Register<DeleteUserParams>(DeleteUser);
    
    repo.Run(new CreateUserParams { Name = "test" });
    repo.Run(new DeleteUserParams { Name = "test" });
    

如前所述,可以使用ComandRepository 中的Dictionary&lt;Type, Action&gt; 实现相同的行为,但是我必须转换方法参数,或者,如果我使用接口而不是Action,则转换为IFoo&lt;T&gt; 获取字典项后。

我的问题是:可以(ab-)使用这样的泛型类型吗(考虑到T 的大量可能值)?

(额外问题)如果不行,为什么不呢?这会导致什么成本/负面影响?

最后一点:我意识到这不适用于类型层次结构或接口。 T 类型必须完全匹配,这在我的场景中很好。

编辑:我发现Jil,一个 JSON 序列化器,也依赖于这种模式。请参阅TypeCache 类型,它存储一个委托以序列化T 类型的对象(据我通过浏览代码了解到)。由于这个TypeCache 将存储大量类型,我认为该模式通常没有问题。

了解类型或其静态成员是否需要被垃圾收集,或者是否需要考虑其他性能影响仍然很有趣。

【问题讨论】:

  • 这不是我们应该使用泛型的方式吗?他们被要求做那样的​​事情,对吗?
  • “通用”表示相同代码用于满足约束的所有类型。如果你想为不同的类型使用不同的代码,泛型很可能是错误的工具。
  • 你的所有类型都是原始的吗?如果是,请查看我的回答 here
  • 这正是我工作中的几个系统的工作方式。我看不出它是如何“滥用”的,这对我来说似乎是使用泛型的好方法。对我们来说,它用于处理命令的域模型。每个命令显然需要不同的处理,因此我们将处理程序方法注册到命令类型。
  • @Dennisch 我认为这可能是滥用的原因是我最终会创建大量的泛型类型(Commands&lt;T&gt;),我不确定这是否会造成任何麻烦。 (编辑)我打算以与您描述的方式类似的方式使用此模式。很高兴知道其他人正在这样做。

标签: c# generics pattern-matching


【解决方案1】:

您建议的方法是可行的,但缺点是您的存储库实际上是一个单例。如果您发现自己需要一个不像单例的存储库,您可能会发现 ConditionalWeakTable[1] 类型很有帮助。使用其中一个的技巧是,对于每种感兴趣的类型,您都会有一个单例ConditionalWeakTable,它将您的对象映射到与该类型关联的事物(如果有的话)。该类仅在 .NET 4.0 中可用,但可以做一些很棒的事情。

[1]http://msdn.microsoft.com/en-us/library/dd287757.aspx

举个例子,假设需要一个类型OutTypeKeyedDictionary,它支持SetValue&lt;T&gt;(T Value)bool TryGetValue&lt;T&gt;(out T Value)。可以使用一个静态类族OutputMappers&lt;T&gt;,每个类都拥有一个ConditionalWeakTable&lt;OutTypeKeyedDictionary, T&gt; 的单例实例。 OutTypeKeyedDictionary 实际上没有任何字段(!);相反,每个实例将纯粹用作身份令牌,用作ConditionalWeakTable 密钥。顺便说一句,类位于CompilerServices 命名空间而不是Collections 的原因是它被ExpandoObject 之类的东西大量使用。

【讨论】:

  • 非常有趣!第一次听说这种类型。不过,我不太确定我是否正确理解了意思;在上面的示例中,您是否建议我在 Commands&lt;T&gt; 类型中使用 static readonly ConditionalWeakTable&lt;CommandRepository, Action&lt;T&gt;&gt; 字段,然后将操作映射到 CommandRepository 实例以解决单例问题?换句话说,ConditionalWeakTable 的全部意义在于允许多个CommandRepository 实例? (这将是一个巨大的进步)
  • 还有一点:我想ConditionalWeakTable 或多或少是一个字典,它使用WeakReference 作为它的键——你碰巧知道是不是这样吗?或者,如果不是,您是否知道还有什么其他魔法在推动该类型/是否有任何性能考虑需要考虑?否则,这听起来像是一个理想的选择,无论单例解决方案是否足够。
  • @enzi:见上面的更新。此外,ConditionalWeakTable 使用了一些特殊的魔法(在 4.0 之前不可用)来确保存在对键的任何类型的引用(强、终结器队列等)也将被视为存在于值中。我不知道确切的性能考虑是什么,但它的设计用途与我描述的非常相似。
  • @enzi:上述方法的一个小限制是,没有实用的方法可以找到与特定OutTypeKeyedDictionary 实例关联的所有数据,因为它分散在许多不同的表中,并且该实例不包含对其中任何一个的引用。有一些方法可以解决这个问题,但它们有点棘手。
  • 感谢您的更新。我认为您提到的限制是很自然的;如果我将值存储在Type&lt;T&gt; 中,我将如何能够枚举这些值?通过枚举T 的所有可能值并检查它是否包含某些内容(不切实际)或使用某种支持集合,我想——我不打算这样做,所以这对我来说没有问题。谢谢您的回答;我会在一天左右的时间里不回答这个问题,如果没有其他答案,我会接受你的。
【解决方案2】:

在像 C# 这样只提供单次分派的语言中实现双重分派的常用方法是Visitor pattern。您的示例如下所示:

接口:

interface IVisitor
{
    void VisitCreateUserParams(CreateUserParams p);

    void VisitDeleteUserParams(DeleteUserParams p);
}

interface IParams
{
    void Accept(IVisitor visitor);
}

命令参数:

class CreateUserParams : IParams
{
    public void Accept(IVisitor visitor) { visitor.VisitCreateUserParams(this); }

    public string Name { get; set; }
}

class DeleteUserParams : IParams
{
    public void Accept(IVisitor visitor) { visitor.VisitDeleteUserParams(this); }

    public string Name { get; set; }
}

命令:

class CommandHandler : IVisitor
{
    public void VisitCreateUserParams(CreateUserParams p)
    {
        Console.WriteLine("Creating " + p.Name);
    }

    public void VisitDeleteUserParams(DeleteUserParams p)
    {
        Console.WriteLine("Deleting " + p.Name);
    }
}

示例用法:

var handler = new CommandHandler();

new CreateUserParams { Name = "test" }.Accept(handler);
new DeleteUserParams { Name = "test" }.Accept(handler);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-31
    • 1970-01-01
    相关资源
    最近更新 更多