2013-04-25 25 views
13

在C#中,我有一個使用generics傳入T的功能,我想運行一個檢查,看看是否Tobject實現一個interface,如果這樣稱呼上interfacemethods之一。我不希望T限制只能是該類型。是否有可能做到這一點?在C#中,如果我的對象支持那個接口,我該如何檢查T是否是IInterface類型並將其轉換爲該類型?

例如:

public class MyModel<T> : IModel<T> where T : MyObjectBase 
{ 
    public IQueryable<T> GetRecords() 
    { 
     var entities = Repository.Query<T>(); 
     if (typeof(IFilterable).IsAssignableFrom(typeof(T))) 
     { 
      //Filterme is a method that takes in IEnumerable<IFilterable> 
      entities = FilterMe(entities)); 
     } 
     return entities; 
    } 

    public IEnumerable<TResult> FilterMe<TResult>(IEnumerable<TResult> linked) where TResult : IFilterable 
    { 
     var dict = GetDict(); 
     return linked.Where(r => dict.ContainsKey(r.Id)); 
    } 
} 

即我正的錯誤是:

錯誤21類型「TResult」不能在通用類型或方法被用作類型參數「TResult」' FilterMe(System.Collections.Generic.IEnumerable)」。沒有從'TResult'到'IFilterable'的隱式引用轉換。

+2

_NEVER_在泛型類中放置一個泛型方法,其中方法的泛型類型參數與類的名稱相同,只是要求麻煩。 – 2013-04-26 03:06:50

+0

@Jeff - 那實際上是一個錯字。 。我已經更新了這個問題 – leora 2013-04-26 03:22:42

+0

爲什麼只有某個類實現了IFilterable?爲什麼不是所有的類都使用空實現進行篩選? – 2013-05-01 07:53:45

回答

17

缺少的部分是Cast<>()

if(typeof(IFilterable).IsAssignableFrom(typeof(T))) { 
    entities = FilterMe(entities.Cast<IFilterable>()).AsQueryable().Cast<T>(); 
} 

注意使用Cast<>()到實體列表轉換爲正確的亞型。除非T執行IFilterable,否則這個投射將失敗,但由於我們已經檢查過,所以我們知道它會。

+0

但這仍然不能編譯 – leora 2013-04-25 04:40:30

+0

@leora:什麼是編譯錯誤?您可能需要將'FilterMe'的結果通過另一次調用'Cast'投入到'T'。Cast ()' – siride 2013-04-25 16:53:54

+0

我得到錯誤,指出T必須有約束條件IFilterable,但這正是我想要避免的條件 – leora 2013-04-26 02:18:15

15
if (typeof(IMyInterface).IsAssignableFrom(typeof(T)) 

這將檢查是否IMyInterface類型的變量可以從T類型的實例被分配。

+0

這很好,但我怎麼能然後調用接口上的方法,如果該方法不是在所有T約束對象。 。如果我嘗試調用它。 。我仍然得到「你必須刪除這個約束的錯誤」 – leora 2013-04-25 03:36:34

+0

我給這個問題添加了一些代碼,以明確我想要做什麼 – leora 2013-04-25 03:46:13

+0

你應該發佈你的課程。我懷疑你的班級有一些不適當的限制。 – 2013-04-25 03:50:11

3

如果您有一個通用類型的參數,可能會或可能不會執行IFoo,它可能將as轉儲到IFoo類型的存儲位置;如果你這樣做了,你可以將它傳遞給任何需要IFoo的方法,以及任何需要泛型參數限制爲IFoo的方法,但是如果你這樣做的話你將失去所有泛型類型信息 - 參數將被傳遞如IFoo。除此之外,這意味着如果你的原始對象是一個結構,它將被裝箱。

如果你想測試一個泛型參數類型是否實現IFoo並調用了一個通用的約束IFoo如果這樣做,同時保持原有的泛型類型(這可能如果類型是一個結構是有用的方法,以及可能是必要的,如果類型被傳遞給既有IFooIBar約束的泛型方法,也有可能想要傳遞的東西不共享任何單個常見超類型),則有必要使用Reflection。

例如,假設一個人想有一個方法Zap這需要一個通用的ref參數,調用它Dispose如果它實現IDisposable,並且清除它。如果該參數是IDisposable類類型,則應將空測試作爲原子操作執行,並清除該參數。

public static class MaybeDisposer 
{ 
    static class ClassDisposer<T> where T : class,IDisposable 
    { 
     public static void Zap(ref T it) 
     { 
      T old_it = System.Threading.Interlocked.Exchange(ref it, null); 
      if (old_it != null) 
      { 
       Console.WriteLine("Disposing class {0}", typeof(T)); 
       old_it.Dispose(); 
      } 
      else 
       Console.WriteLine("Class ref {0} already null", typeof(T)); 
     } 
    } 
    static class StructDisposer<T> where T : struct,IDisposable 
    { 
     public static void Zap(ref T it) 
     { 
      Console.WriteLine("Disposing struct {0}", typeof(T)); 
      it.Dispose(); 
      it = default(T); 
     } 
    } 
    static class nonDisposer<T> 
    { 
     public static void Zap(ref T it) 
     { 
      Console.WriteLine("Type {0} is not disposable", typeof(T)); 
      it = default(T); 
     } 
    } 
    class findDisposer<T> 
    { 
     public static ActByRef<T> Zap = InitZap; 
     public static void InitZap(ref T it) 
     { 
      Type[] types = {typeof(T)}; 
      Type t; 
      if (!(typeof(IDisposable).IsAssignableFrom(typeof(T)))) 
       t = typeof(MaybeDisposer.nonDisposer<>).MakeGenericType(types); 
      else if (typeof(T).IsValueType) 
       t = typeof(MaybeDisposer.StructDisposer<>).MakeGenericType(types); 
      else 
       t = typeof(MaybeDisposer.ClassDisposer<>).MakeGenericType(types); 
      Console.WriteLine("Assigning disposer {0}", t); 
      Zap = (ActByRef<T>)Delegate.CreateDelegate(typeof(ActByRef<T>), t, "Zap"); 
      Zap(ref it); 
     } 
    } 
    public static void Zap<T>(ref T it) 
    { 
     findDisposer<T>.Zap(ref it); 
    } 
} 

第一次代碼調用與任何類型的T,這將決定什麼樣的通用靜態類的可以生產該參數並使用反射來創建一個委託來調用泛型類的靜態方法。與T相同類型的後續調用將使用緩存的委託。儘管反射可能會稍微慢一點,但對於任何類型的T只需使用一次。所有後續調用MaybeDisposer.Zap<T>(ref T it)具有相同類型T將直接通過委託調度,並因此快速執行。

需要注意的是,如果給他們泛型類型參數不符合指定的開放式泛型類的約束MakeGenericType的通話將拋出異常(例如,如果T是一個類或沒有實行IDisposable,企圖使泛型類型StructDisposer<T>會拋出異常);這些測試在運行時發生,並且未經編譯器驗證,因此您可以使用運行時檢查來查看類型是否滿足適當的約束條件。請注意,該代碼不會測試it是否執行IDisposable,而是測試T是否執行。這個非常重要。否則,如果MaybeDispose與它舉行了參照StreamObject類型的參數調用,它將確定it實施IDisposable,因此嘗試創建一個ClassDisposer<Object>,轟然因爲Object沒有實現IDisposable

1

OfType(...)方法(link)你在找什麼?

public class MyModel<T> : IModel<T> where T : MyObjectBase 
{ 

    public IQueryable<T> GetRecords() 
    { 
     var entities = Repository.Query<T>(); 
     if (typeof(IFilterable).IsAssignableFrom(typeof(T))) 
     { 
      //Filterme is a method that takes in IEnumerable<IFilterable> 
      entities = FilterMe(entities)); 
     } 
     return entities; 
    } 

    public IEnumerable<TResult> FilterMe<TResult>(IEnumerable<TResult> linked) where TResult : IFilterable 
    { 
     var dict = GetDict(); 
     return linked.OfType<TResult>().Where(r => dict.ContainsKey(r.Id)); 
    } 
} 
1

在我的回答,我認爲方法FilterMe在內部使用,而不應該是你的模型之外可見和可標明private。如果我的假設錯誤,您可以創建FilterMe的私人超載。

在我的回答中,我剛剛刪除了通用<TResult>。我認爲這個FilterMe總是大約那麼T類型的實體(因爲它在同一個Model類中)。這解決了關於在TTResult之間進行鑄造的問題。 TResult不必被標記爲IFilterable,因爲沒有使用IFilterable的成員。並且由於代碼已經檢查,如果TIFilterable爲什麼再次檢查(特別是當FilterMe將是私人的)?

public IQueryable<T> GetRecords() 
    { 
     var entities = Repository.Query<T>(); 
     if (typeof(IFilterable).IsAssignableFrom(typeof(T))) 
     { 
      //Filterme is a method that takes in IEnumerable<IFilterable> 
      entities = FilterMe(entities).AsQueryable(); 
     } 
     return entities; 
    } 

    public IEnumerable<T> FilterMe(IEnumerable<T> linked) 
    { 
     var dict = GetDict(); 
     return linked.Where(r => dict.ContainsKey(r.Id)); 
    } 

如果你想創建第二個FilterMe,與Queryable<T>更換IEumerable<T>類型,所以你不必你的實體AsQueryable()轉換。

2

我能想出的最簡單的形式是一樣的東西:

public IEnumerable<T> GetRecords() 
{ 
    IQueryable<T> entities = new List<T>().AsQueryable(); 

    if (typeof(IFilterable).IsAssignableFrom(typeof(T))) 
    { 
     entities = FilterMe<IFilterable, T>(entities.OfType<IFilterable>()).AsQueryable(); 
    } 

    return entities; 
} 

public IEnumerable<TResult> FilterMe<TSource, TResult>(IEnumerable<TSource> linked) where TSource : IFilterable 
{ 
    return linked.Where(r => true).OfType<TResult>(); 
} 

這裏的關鍵是需要有類型的通入,並取回該方法的。我不得不在本地更改類型以使其工作。

OfType將靜靜地過濾掉並非真正屬於給定類型的項目,因此它假設它是任何一個調用中相同類型的集合。

因爲您正在從FilterMe重新分配,您仍然需要接口可分配檢查。

1
public IEnumerable<TResult> FilterMe<TResult>(IEnumerable<TResult> linked) where TResult : IFilterable 
{ 
    var dict = GetDict(); 
    return linked.Where(r => dict.ContainsKey(r.Id)); 
} 

嘗試用此版本替換FilterMe:

public IEnumerable<T> FilterMe(IEnumerable<IFilterable> linked) 
{ 
    var dict = GetDict(); 
    return linked.Where(r => dict.ContainsKey(r.Id)).Cast<T>(); 
} 

然後,你打電話時,你的代碼改成這樣:

if (typeof(IFilterable).IsAssignableFrom(typeof(T))) 
{ 
    //Filterme is a method that takes in IEnumerable<IFilterable> 
    var filterable = entities.Cast<IFilterable>(); 
    entities = FilterMe(entities).AsQueryable(); 
} 
1

你不必讓FilterMe方法一個通用的方法來實現相同的結果。

public class MyModel<T> : IModel<T> where T : MyObjectBase { 
     public IQueryable<T> GetRecords() 
    { 
     var entities = Repository.Query<T>(); 
     if (typeof(IFilterable).IsAssignableFrom(typeof(T))) 
     { 
      //Filterme is a method that takes in IEnumerable<IFilterable> 
      entities = FilterMe(entities.Cast<IFilterable>()); 
     } 
     return entities; 
    } 

     public IEnumerable<T> FilterMe(IEnumerable<IFilterable> linked) { 
      var dict = GetDict(); 
      return linked 
        .Where(r => dict.ContainsKey(r.Id)) 
        .Cast<T>(); 
     } 
    } 
相關問題