用于静态generics类?

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

例如

public static class Example { public static ... } 

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

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

静态通用类作为字典


给出的答案摘要

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

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

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

我使用静态generics类来缓存reflection繁重的代码。

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

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

使类static不添加任何function – 如果您打算使用类而不实例化它,这只是一个方便的检查。 这有几个用途……

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

例如:

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

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

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

这样,您可以让类型推断在参数类型上工作,并仅指定返回类型。

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


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

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

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

 //Hash any object: FieldwiseHasher.Hash(myCustomStructOrClass); //implementation: public static class FieldwiseHasher { public static int Hash(T val) { return FieldwiseHasher.Instance(val); } } public static class FieldwiseHasher { public static readonly Func Instance = CreateLambda().Compile(); //... } 

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

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

 public static TypeFactory 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.GetInstance(); int b = (int)TypeFactory.GetInstance(); 

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

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

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

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

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

 public static class Repository { public static T[] FindAll { } public static T GetById(int id){ } public static void Save(T item) { } } 

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

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

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

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

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

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

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

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

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

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


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

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

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

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

并在我的unit testing中使用它们 – 节省了大量时间并可以将它们用于任何实体类型:

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

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

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