用于静态泛型类?

时间:2010-04-21 17:15:23

标签: c# generics static

C#中静态通用类的主要用途是什么?什么时候应该使用它们?哪些例子最能说明它们的用法?

e.g。

public static class Example<T>
{
   public static ...
}

由于您无法在其中定义扩展方法,因此它们的实用程序似乎有些受限。关于这个主题的网络参考很少,所以很明显没有很多人使用它们。这是一对夫妇: -

http://ayende.com/Blog/archive/2005/10/05/StaticGenericClass.aspx

Static Generic Class as Dictionary


给出的答案摘要

关键问题似乎是“静态泛型类与静态方法非泛型静态类与静态泛型成员之间有什么区别?”

关于使用哪个的决定似乎围绕“类是否需要在内部存储特定于类型的状态?”

如果不需要特定于类型的内部存储,那么带有通用静态方法的静态非泛型类似乎更可取,因为调用语法更好,您可以在其中定义扩展方法。

9 个答案:

答案 0 :(得分:24)

我使用静态泛型类来缓存反射密码。

假设我需要构建一个实例化对象的表达式树。我在类的静态构造函数中构建它一次,将其编译为lambda表达式,然后将其缓存在静态类的成员中。我经常不会公开评估这些课程 - 他们通常是其他课程的助手。通过以这种方式缓存我的表达式,我避免了在某种Dictionary<Type, delegate>中缓存表达式的需要。

BCL中有一个这种模式的例子。 (DataRow)扩展方法Field<T>()SetField<T>()使用(私有)静态泛型类System.Data.DataRowExtensions+UnboxT<T>。用Reflector检查一下。

答案 1 :(得分:12)

创建类static不会添加任何功能 - 如果您打算在不实例化的情况下使用类,这只是一个方便的检查。有几种用途......

您可以使用静态泛型类来解决限制:C#不允许部分特化。这意味着您必须指定所有类型参数或不指定。然而,这可能是不必要的冗长。

例如:

static class Converter {
    public TOut Convert<TIn, TOut>(TIn x) {...}
}

前一个类不允许类型推断,因为推理对返回值不起作用。但是,如果不指定输入类型,也无法指定返回值类型,因为您无法进行部分特化。使用(可能是静态的)泛型类,您只能指定两种类型中的一种:

static class ConvertTo<TOut> {
    public TOut Convert<TIn>(TIn x) {...}
}

通过这种方式,您可以让类型推断适用于参数类型,并仅指定返回类型。

(虽然上面的情况是可以想象的,但它当然不要求泛型类是静态的。)


其次,(作为Steven first pointed out)每个构造类型都存在一个单独的静态字段,这使静态类成为存储有关类型或类型组合的额外信息的绝佳位置。从本质上讲,它是一个半静态哈希表,可以键入类型。

键入类型的半静态查找表听起来有些小,但它实际上是一个非常非常有用的结构,因为它允许您存储昂贵的反射和代码生成结果,它们几乎可以自由查找(更便宜)而不是字典,因为它被JIT编入,你避免调用.GetType())。如果你正在进行元编程,这很棒!

例如,我在ValueUtils中使用它来存储生成的哈希函数:

//Hash any object:
FieldwiseHasher.Hash(myCustomStructOrClass);

//implementation:
public static class FieldwiseHasher {
    public static int Hash<T>(T val) { return FieldwiseHasher<T>.Instance(val); }
}

public static class FieldwiseHasher<T> {
    public static readonly Func<T, int> Instance = CreateLambda().Compile();
    //...
}

静态通用方法允许类型推断使用非常简单;泛型类上的静态字段允许几乎无开销的存储(元)数据。如果像DapperPetaPoco之类的ORM使用这样的技术,那就不会让我感到惊讶;但它对(de)序列化器也很有用。一个限制是你得到的开销很低,因为你绑定了编译时类型;如果传递的对象实际上是子类的实例,那么您可能绑定到错误的类型 - 并添加检查以避免这种类型破坏了低开销的好处。

答案 2 :(得分:10)

泛型类型的静态字段特定于实际类型T.这意味着您可以在内部存储特定于类型的缓存。这可能是创建静态泛型类型的原因。这是一个(相当无用但信息丰富)的例子:

public static TypeFactory<T> where T : new()
{
    // There is one slot per T.
    private static readonly object instance = new T();

    public static object GetInstance() {
        return instance;
    }
}

string a = (string)TypeFactory<string>.GetInstance();
int b = (int)TypeFactory<int>.GetInstance();

答案 3 :(得分:5)

我最近学到的静态泛型类的一种用法是在类的类级别上定义约束,然后约束适用于类的所有静态成员,我还学到了这不是允许定义扩展方法的静态泛型类。

例如,如果您要创建一个静态泛型类,并且您知道所有泛型类型都应该是IComparable(只是一个示例),那么您可以执行以下操作。

static class MyClass<T> where T : IComparable
{
  public static void DoSomething(T a, T b)
  {
  }

  public static void DoSomethingElse(T a, T b)
  {
  }
}

请注意,我不需要将约束应用于所有成员,而只是在类级别。

答案 4 :(得分:3)

您提到的第一篇博文显示了有效用途(作为ActiveRecord实现的静态存储库类):

public static class Repository<T>
{
    public static T[] FindAll { }

    public static T GetById(int id){ }

    public static void Save(T item) { }
}

或者,如果您想为通用数组实现不同的排序方法:

public static class ArraySort<T>
{
    public static T[] BubbleSort(T[] source) { }

    public static T[] QuickSort(T[] source) { }
}

答案 5 :(得分:2)

  

静态的主要用途是什么   C#中的泛型类?什么时候应该   使用?最好的例子是什么   他们的用法?

我认为一般来说,你应该避免在静态类上创建类型参数,否则你不能依赖类型推断来使你的客户端代码更简洁。

举一个具体的例子,假设您正在编写一个静态实用程序类来处理Lists上的操作。你可以用两种方式写课:

// static class with generics
public static class ListOps<T>
{
    public static List<T> Filter(List<T> list, Func<T, bool> predicate) { ... }
    public static List<U> Map<U>(List<T> list, Func<T, U> convertor) { ... }
    public static U Fold<U>(List<T> list, U seed, Func<T, U> aggregator) { ... }
}

// vanilla static class
public static class ListOps
{
    public static List<T> Filter<T>(List<T> list, Func<T, bool> predicate) { ... }
    public static List<U> Map<T, U>(List<T> list, Func<T, U> convertor) { ... }
    public static U Fold<T, U>(List<T> list, U seed, Func<T, U> aggregator) { ... }
}

但是类是等价的,但哪一个更容易使用?比较:

// generic static class
List<int> numbers = Enumerable.Range(0, 100).ToList();
List<int> evens = ListOps<int>.Filter(numbers, x => x % 2 = 0);
List<string> numberString = ListOps<int>.Map(numbers, x => x.ToString());
int sumOfSquares = ListOps<int>.Fold(numbers, 0, (acc, x) => acc + x*x);

// vanilla static class
List<int> numbers = Enumerable.Range(0, 100).ToList();
List<int> evens = ListOps.Filter(numbers, x => x % 2 = 0);
List<string> numberString = ListOps.Map(numbers, x => x.ToString());
int sumOfSquares = ListOps.Fold(numbers, 0, (acc, x) => acc + b * b);

在我看来,ListOps<someType>笨重而且笨拙。 vanilla类的定义略大,但客户端代码更容易阅读 - 这要归功于类型推断。

在最坏的情况下,C#无法推断出类型,您必须手动指定它们。您想写ListOps<A>.Map<B>(...)还是ListOps.Map<A, B>(...)?我倾向于后者。


当您的静态类持有无可变状态,或者其可变状态在编译时已知时,上述策略特别有效。

如果静态类包含在编译时未确定其类型的可变状态,那么您可能有一个带有泛型参数的静态类的用例。希望这些场合很少,但是当它发生时,你会很高兴C#支持这个功能。

答案 6 :(得分:2)

在针对使用EntityFramework异步方法的类进行测试时,我使用这些来模拟DbSet。

public static class DatabaseMockSetProvider<TEntity> where TEntity: class
{
    public static DbSet<TEntity> CreateMockedDbSet(IQueryable<TEntity> mockData)
    {
        var mockSet = Mock.Create<DbSet<TEntity>>();
        Mock.Arrange(() => ((IDbAsyncEnumerable<TEntity>)mockSet).GetAsyncEnumerator())
            .Returns(new TestDbAsyncEnumerator<TEntity>(mockData.GetEnumerator()));
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).Provider)
            .Returns(new TestDbAsyncQueryProvider<TEntity>(mockData.Provider));
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).Expression).Returns(mockData.Expression);
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).ElementType).Returns(mockData.ElementType);
        Mock.Arrange(() => ((IQueryable<TEntity>)mockSet).GetEnumerator()).Returns(mockData.GetEnumerator());

        return mockSet;
    }
}

在我的单元测试中使用它们 - 节省了大量时间,可以将它们用于任何实体类型:

var mockSet = DatabaseMockSetProvider<RecipeEntity>.CreateMockedDbSet(recipes);
        Mock.Arrange(() => context.RecipeEntities).ReturnsCollection(mockSet);

答案 7 :(得分:1)

你是对的:它们用得不多。但是,也许有一些罕见的情况是例外。例如,如果类是特定于类型的存储库,如Justin的示例,但将静态集合保存为缓存,该怎么办?一般来说,如果它包含状态,而不仅仅是方法,那么可能有一点。

答案 8 :(得分:0)

静态泛型类与任何给定的静态类完全一样有用。不同之处在于,您不必使用复制和粘贴为您希望它处理的每种类型创建静态类的版本。您使该类具有通用性,并且您可以为每组类型参数“生成”一个版本。