C# - 泛型方法与非泛型方法

9

我有点困惑为什么/何时需要使用泛型方法,因为非泛型方法可以访问其包含类的泛型成员并传递泛型参数。

因此,使用一个可能没有意义但突出我提出这个问题的示例,我该如何做:

public class SomeGeneric<T>
{
    public T Swap<T>(ref T a, ref T b)
    {
        T tmp = a;
        a = b;
        b = tmp;
    }
}

过载
public class SomeGeneric<T>
{
    public T Swap(ref T a, ref T b)
    {
        T tmp = a;
        a = b;
        b = tmp;
    }
}

这个?

或者,为什么我想要使用通用方法


2
在大多数情况下,方法或类中的一个是泛型的,很少两者都是。在第一个示例中,T在类和方法定义之间是不明确的,它们应该有不同的名称,但从语义上讲,您不希望有不同的类型。需要在类和方法级别上具有两个泛型参数可能会发生,但这种情况很少见。 - Servy
6个回答

9
您通常会在非泛型类型中使用通用方法。
例如,请看Enumerable。它定义了大部分LINQ功能的通用扩展方法,但它本身不是泛型的。
您可能还希望在泛型类型中使用通用方法,但仅当通用方法使用不同的泛型类型说明符时。
这使您可以编写类似以下内容的代码:
 class Foo<T> where T : IConvertible, IComparable<T>
 {
      int CompareTo<U>(U other) where U : IConvertible
      {
           // Convert to this
           T otherConverted = Convert.ChangeType(other, typeof(T));
           return this.CompareTo(otherConverted);
      }
 }

(尽管这有点牵强,但对于一个与double等进行比较的Foo<int>确实可以编译并正确工作。)

4
如果包含类不是泛型的,那该怎么办?如果它具有不同的泛型类型参数呢?

2
第一个示例并没有太多意义,因为没有使用class参数。不过考虑另一个示例:
public class SomeGeneric<T>
{
    public K ConvertTo<T>(T a)
    {
         return CodeThatConvertsTtoK(a);
    }
}

它的用法如下: new SomeGeneric<int>().ConvertToInt("ten");

意思是创建一个泛型类SomeGeneric,类型为int,并调用其中的ConvertToInt方法将字符串"ten"转换成整数。

1
应该是 public K ConvertTo<K>(T a) (现在这样编译器会报错)。 - Reed Copsey

1

方法级别的类型参数常见于扩展方法,因为它们必须在非泛型静态类中声明。但是对于非泛型类型中的每个泛型成员都需要它们。

public static class Extensions
{
    public static void Foo<A, B>(this A a, B b) { [...] }

    public static T Bar<T>(this String input) { [...] }

    public static U FooBar<V, W>(this V v, W w) { [...] }
}

1

如果类和方法都是泛型的话,类型参数(“泛型参数”)必须有不同的名称。当然,像你第一个例子中那样,不能有两个名为T的不同事物。

如果您的方法是非静态的(似乎是这样),如果选择使包含类成为泛型,则在实例化类时必须已经指定类型。例如:var obj = new SomeGeneric<DateTime>();。因此,它应该是逻辑上“属于”类建模对象的东西。

如果您的方法是静态的,并且选择使类成为泛型,则仍然必须以某种方式与类一起指定类型参数。如果从类外调用该方法,则会像这样进行:SomeGeneric<DateTime>.Swap(ref a, ref b);

方法泛型化的优点在于,在许多情况下,您可以使用类型推断,从而允许您省略带有尖括号的类型参数。这只适用于泛型方法。例如:nonGeneric.Swap(ref a, ref b);其中Swap<T>方法是泛型的。编译器将查看ab的编译时类型,并找出哪个T适合,而无需您指定它。
结论:如果T在逻辑上不属于类(如List<T>),请将其与方法放在一起。

0

下面是一个真正发挥泛型方法优势的例子,考虑一个二叉树表达式,例如 1 + 2。你想要在整个树上实现访问者模式,目标是进行某种类型的映射/归约操作。例如:

  • 将一个表达式归约为 字符串 以打印它
  • 将一个表达式归约为 double 以计算其值
  • 将一个表达式映射到另一个表达式,并改变、添加或删除一些成员

所有这些操作都可以放在一个访问者模式方法后面:

public abstract class Expression
{
  public abstract T Reduce<T>(ITransformer<T> transformer);
}

这类似于经典的访问者模式实现,但术语已更改:我们使用Reduce()代替Accept(),并使用ITransformer<T>代替IVisitor。请注意,该方法是通用的。

这种方法允许我们创建任意数量的ITransformer<T>类,将层次结构转换为任何类型T,支持映射-减少操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接