【问题标题】:Is there a name for this pattern? (C# compile-time type-safety with "params" args of different types)这种模式有名字吗? (具有不同类型的“参数”参数的 C# 编译时类型安全)
【发布时间】:2015-07-17 03:20:28
【问题描述】:

这种模式有名字吗?

假设您要创建一个方法,该方法采用可变数量的参数,每个参数必须是一组固定类型中的一个(以任何顺序或组合),其中一些类型您无法控制。一种常见的方法是让您的方法采用 Object 类型的参数,并在运行时验证类型:

void MyMethod (params object[] args)
{
    foreach (object arg in args)
    {
        if (arg is SomeType)
            DoSomethingWith((SomeType) arg);
        else if (arg is SomeOtherType)
            DoSomethingElseWith((SomeOtherType) arg);
        // ... etc.
        else throw new Exception("bogus arg");
    }
}

但是,假设您像我一样痴迷于编译时类型安全,并且希望能够在编译时验证您的方法的参数类型。这是我想出的一种方法:

void MyMethod (params MyArg[] args)
{
    // ... etc.
}

struct MyArg
{
    public readonly object TheRealArg;

    private MyArg (object obj) { this.TheRealArg = obj; }

    // For each type (represented below by "GoodType") that you want your 
    // method to accept, define an implicit cast operator as follows:

    static public implicit operator MyArg (GoodType x)
    { return new MyArg(x); }

}

隐式转换允许您将有效类型的参数直接传递给您的例程,而无需显式转换或包装它们。如果您尝试传递不可接受类型的值,则会在编译时捕获错误。

我相信其他人已经使用过这种方法,所以我想知道这种模式是否有名称。

【问题讨论】:

  • 我可能遗漏了一些东西,但我没有看到任何规律。
  • 你知道,你可以让“必需类型”实现一个通用接口,然后取一个params IWhatever[] args
  • @Etienne 你不能将接口改装成string 之类的东西......
  • @Ed 我认为如果您必须提出一个不寻常的解决方案,那就是过度工程,因为这通常意味着您最初解决问题的方式是错误的。更简单的解决方案是不需要使用类型开关的可变参数函数。
  • 你知道这让我想起了什么吗? php。即使 PHP 是动态类型的,而 C# 是静态类型的。

标签: c# params type-safety


【解决方案1】:

Interwebs 上似乎没有命名模式,但根据 Ryan 对您的问题的评论,我认为该模式的名称应该是 Variadic Typesafety .

一般来说,我会非常谨慎地使用它,但我不会判断该模式的好坏。许多评论者都提出了有利和不利的观点,我们在其他模式中看到了这一点,例如 FactoryService LocatorDependency Injection、MVVM 等等。这都是关于上下文的。所以这里是一个刺...

上下文

必须处理一组可变的不同对象。

何时使用

  1. 您的方法可以接受可变数量的不同类型的参数,这些类型没有共同的基本类型。
  2. 您的方法被广泛使用(即在代码中的许多地方和/或您的框架的大量用户。关键是类型安全提供了足够的好处来保证其使用。
  3. 参数可以按任何顺序传递,但不同类型的集合是有限的,并且是该方法唯一可接受的集合。
  4. 表现力是您的设计目标,您不想让用户承担创建包装器或适配器的责任(请参阅替代方案)。

实施

你已经提供了。

示例

  • LINQ to XML(例如new XElement(...)
  • 其他构建器,例如构建 SQL 参数的构建器。
  • 处理器外观(例如,那些可以接受来自不同框架的不同类型的委托或命令对象的外观)来执行命令,而无需创建显式的命令适配器。

替代品

  • 适配器。接受该方法可以使用的可变数量的某些适配器类型的参数(例如Adapter<T> 或非泛型Adapter 的子类)以产生所需的结果。这扩大了您的方法可以使用的集合(类型不再是有限的),但是如果适配器做正确的事情以使处理仍然有效,则不会丢失任何内容。缺点是用户有额外的负担来指定现有的和/或创建新的适配器,这可能会偏离意图(即增加“仪式”,并削弱“本质”)。
  • 删除类型安全。这需要接受一个非常基本的类型(例如Object)并进行运行时检查。知道传递什么的负担被传递给用户,但代码仍然具有表现力。错误直到运行时才会暴露出来。
  • 复合。传递由其他对象组合而成的单个对象。这需要进行方法调用前的构建,但会退回到对组合集合中的项目使用上述模式之一。
  • 流畅的 API。将单个调用替换为一系列特定调用,每个调用对应一种可接受的参数。一个典型的例子是StringBuilder

【讨论】:

  • StringBuilder ...很好的例子。我会赞成重载方法。您提到了 Expressiveness - 让我想知道自定义派生 Expression 的可能性。也许是 ParameterTree ...有点“即兴”的想法。
【解决方案2】:

它被称为反模式,通常称为Poltergeist

更新:

如果 args 的类型始终是常量并且顺序无关紧要,则创建重载,每个都采用集合 (IEnumerable) 更改每个方法中的 T 为您需要操作的类型。这将通过以下方式降低您的代码复杂性:

  1. 删除 MyArg 类
  2. 无需在 MyMethod 中进行类型转换
  3. 将添加额外的编译时类型安全性,如果您尝试使用无法处理的 args 列表调用该方法,则会出现编译器异常。

【讨论】:

  • 我不同意,这可能在图书馆中很有用。类型安全ftw。
  • 这个构造函数 "private MyArg (object obj) { this.TheRealArg = obj; }" 消除了编译时类型安全的任何可能性。 MyMethod 中的强制转换也消除了 DoSomethingWith 和 DoSomethingElseWith 方法免费获得的编译时类型安全性。
  • 呃,如果它们不是恒定的呢?我想这样调用我的函数:f(10, "hello", new Exception("bugger it")),我只希望它接受这些类型。唯一有效的反对理由是说你永远不需要这样做。我建议它在提供库调用方法时可能很有用,我现在正在使用它,我认为它有点整洁。
  • @Charles:你关心的构造函数是private,所以它只能被批准的隐式转换操作符调用。
  • @Gabe - 错过了那个。从我的答案中删除了类型安全信息
【解决方案3】:

这看起来像是Composite Pattern子集。引用自维基百科:

复合模式描述了一组对象将被视为一个对象的单个实例。

【讨论】:

  • 可能,这是我所知道的最接近这种情况的 GoF 模式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-23
  • 1970-01-01
  • 2017-12-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多