2010-04-21 127 views
21

什麼是Static的關鍵用途 C#中的泛型類?他們什麼時候應該使用?哪些例子最能說明他們的用法?用於靜態泛型類?

例如

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

由於您無法在其中定義擴展方法,因此它們的效用似乎有所限制。關於這個話題的Web引用很少,所以很少有人使用它們。這裏有一對夫婦: - 答案的

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

Static Generic Class as Dictionary


摘要鑑於

的主要問題似乎是「什麼是具有靜態靜態泛型類的區別方法具有靜態通用成員的非泛型靜態類?「

關於使用哪個的決定似乎圍繞着「該類需要在內部存儲特定於類型的狀態嗎?」

如果不需要特定於類型的內部存儲,那麼帶有泛型靜態方法的靜態非泛型類似乎更可取,因爲調用語法更好,並且您可以在其中定義擴展方法。

回答

18

我使用靜態泛型類來緩存反射繁重的代碼。

假設我需要構建一個實例化對象的表達式樹。我在類的靜態構造函數中構建一次,將其編譯爲一個lambda表達式,然後將其緩存到靜態類的成員中。我經常不會讓這些課程公開評估 - 他們通常是其他課程的助手。通過以這種方式緩存我的表達式,我避免了在某種Dictionary<Type, delegate>中緩存我的表達式的需要。

BCL中有一個這種模式的例子。 (DataRow)擴展方法Field<T>()SetField<T>()使用(私有)靜態泛型類System.Data.DataRowExtensions+UnboxT<T>。用反射器檢查一下。

+1

將靜態lambda表達式創建構造函數的示例作爲答案的一部分將是有益的。 – Jim 2016-07-12 07:16:25

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) { } 
} 
+2

我正在尋找更多的一般性指導時,他們是有益的。你的#2如何比其中使用泛型方法的非泛型靜態類更好? - public static T [] BubbleSort (T [] source)? – 2010-04-21 17:35:06

+0

@Hightechrider如果需要,staic類將允許您共享與T類型相匹配的私有靜態資源。否則,它只會爲您節省一些擊鍵。 – 2010-04-21 18:44:42

1

你說得對:他們用處不大。儘管如此,也許有一些罕見的情況是例外。例如,如果該類是類型特定的存儲庫(如Justin的示例中那樣),但將靜態集合保存爲緩存會怎麼樣?一般來說,如果它包含狀態,而不僅僅是方法,那麼這可能有一個指向。

+0

謝謝,+1。到目前爲止,我們有:使用它們(A)如果它具有每個派生類型的狀態,或者(B)如果您想要對泛型類型進行約束並且希望輸入更少的類型。是嗎? – 2010-04-21 18:58:03

+0

那麼,當類本身不是通用的,你實際上可以在每個方法的基礎上添加約束,所以這也不是問題。除此之外,我認爲你所擁有的基本上是正確的。 – 2010-04-21 20:15:18

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) 
    { 
    } 
} 

請注意,我不需要將約束應用於所有成員,而只是在類級別。

8

泛型類型的靜態字段特定於實際類型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(); 
+0

非常好。使用靜態通用工廠無需投射到「T」。 – 2016-12-01 13:32:51

0

靜態泛型類與任何給定的靜態類一樣有用。不同之處在於,您不必使用「複製並粘貼」爲您希望使用的每種類型創建靜態類的一個版本。您可以將類設爲通用類,並且可以爲每組類型參數「生成」一個版本。

+0

但是靜態類中的泛型方法給出了幾乎相同的好處(不需要剪切和粘貼來處理任何T),差別似乎是:A)泛型靜態類可以具有「每派生類型」狀態'相對於所有派生類只有一個單一的狀態,B)有時候在類的頂部定義T及其約束有時不那麼簡單。是嗎? – 2010-04-21 19:46:54

+0

你幾乎沒有錯,但你低估了便利性。我剛剛經歷了一個重要的重構工作,需要大量使用泛型類,方法,接口和委託。在某些情況下,您希望使用相同的類型參數,從「實例化」的位置「傳遞類型」。否則,類型不匹配:一種方法中的類型T可能與另一種方法中的類型T不相同。 – 2010-04-21 20:52:42

+0

但是,他們中的任何一個泛型*靜態*類?明顯泛型的*非靜態*類非常有用,因爲你在方法之間共享類型化信息,但是在泛型靜態類中,唯一可以共享的狀態是靜態,這似乎意味着只有非常有限的場景永遠比普通的非靜態類(如果需要時實例化爲單例)或非泛型的具有泛型靜態方法的靜態類更可取。 – 2010-04-21 21:05:06

2

什麼是靜態的關鍵用途 C#中的泛型類?他們應該在什麼時候使用 ?哪些例子最能說明 的使用?

我認爲一般來說,你應該避免在靜態類上創建類型參數,否則你不能依賴類型推斷來使你的客戶端代碼更加簡潔。

舉一個具體的例子,假設你正在編寫一個靜態工具類來處理List上的操作。你可以寫兩種方法:

// 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); 

在我看來,​​笨重笨拙。由於類型推斷,vanilla類的定義稍大,但客戶端代碼更易於閱讀。

在最糟糕的情況下,C#無法推斷出類型,您必須手動指定它們。你願意寫ListOps<A>.Map<B>(...)還是ListOps.Map<A, B>(...)?我偏好後者。


上述策略效果尤其好,當你的靜態類持有沒有可變狀態,或它的可變的狀態是在編譯時知道

如果靜態類保持其類型在編譯時未確定的可變狀態,那麼您可能有一個具有泛型參數的靜態類的用例。希望這些場合少之又少,但是當它發生時,你會很高興C#支持這種功能。

+0

謝謝。我認爲後者也可以變成擴展方法的論點與你的例子特別相關!我沒有得到的一點是關於'類型在編譯時不確定'的最後一節 - 這聽起來像是在編譯時確定的動態而非泛型的用法。這個問題沒有歸結爲「泛型靜態類的每個子類都需要單獨的可變狀態,還是隻存在一個爲所有派生類型存儲的可變狀態?」? – 2010-04-21 20:42:26

10

製作類static不會添加任何功能 - 只是一個方便的檢查,如果您打算使用一個類而不實例化它。這有幾個用途...

您可以使用靜態泛型類來解決某個限制:C#不允許部分特化。這意味着你必須指定所有的類型參數或者沒有。但是,這可能是不必要的冗長。

例如:

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

以前的類不允許類型推斷,因爲推理不上返回值工作。但是,如果不指定輸入類型,也不能指定返回值類型,因爲您無法部分專門化。使用(可能靜態)泛型類,你只能指定兩種類型之一:

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

這樣,你可以讓參數類型類型推斷的工作,並指定只返回類型。

(雖然上面的例子是可以想象的,當然它不需要泛型類是靜態的)。


其次,(如史蒂芬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(); 
    //... 
} 

靜態泛型方法允許類型推斷使使用很容易;泛型類上的靜態字段允許(元)數據的虛擬零開銷。如果ORM像DapperPetaPoco使用這樣的技術,我一點也不會感到意外;但對於(de)序列化程序也很棒。一個限制是你得到低開銷,因爲你綁定到編譯時間類型;如果傳遞的對象實際上是一個子類的實例,那麼您可能會綁定到錯誤的類型 - 並添加檢查以避免這種損害低開銷的好處。

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);