2015-01-12 16 views
7

Ho un dizionario generico che passano ad un metodo che accetta solo IQueryable come parametroGet dizionario originale da dict.AsQueryable()

E 'possibile lanciare il queryable torna al dizionario originale? E non mi riferisco la creazione di un nuovo dizionario con .ToDictionary(...)

private static void Main() 
{ 

    var dict = new Dictionary<int, int>(); 
    dict.Add(1,1); 

    SomeMethod(dict.AsQueryable()); 

} 

public static void SomeMethod(IQueryable dataSource) 
{ 
    // dataSource as Dictionary<int, int> --> null 
    var dict = dataSource.??? 
} 

So che in questo semplice esempio questo non ha molto senso. Ma nel quadro generale ho un interfaccia che mi obbliga a restituire un IQueryable come un DataSource. In fase di implementazione restituisce un dizionario. In un altro posto nel mio codice ho classi che elaborano i dataSource.

Il processore sa che l'origine dati sarà un dizionario, ma io non voglio avere l'overhead per la creazione di un altro dizionario se ho già uno.

risposta

6

Il metodo di estensione .AsQueryable() restituisce un'istanza della EnumerableQuery<T> wrapper class se viene chiamato su qualcosa che non fosse già un IQueryable<T>.

classe Questo wrapper ha una proprietà .Enumerable con internal di accesso che fornisce l'accesso all'oggetto originale che .AsQueryable() è stato chiamato su. Così si potrebbe fare questo per ottenere indietro il dizionario originale:

var dict = new Dictionary<int, int>(); 
dict.Add(1,1); 
var q = dict.AsQueryable(); 



Type tInfo = q.GetType(); 
PropertyInfo pInfo = tInfo.GetProperties(BindingFlags.NonPublic | 
             BindingFlags.Instance) 
          .FirstOrDefault(p => p.Name == "Enumerable"); 
if (pInfo != null) 
{ 
    object originalDictionary = pInfo.GetValue(q, null); 

    Console.WriteLine(dict == originalDictionary); // true 
} 

Tuttavia, questa è generalmente una brutta idea. internal i membri hanno il loro accesso limitato per un motivo, e non credo che ci sia alcuna garanzia che l'implementazione interna di .AsQueryable() non cambierà ad un certo punto in futuro. Quindi la soluzione migliore è trovare un modo per rendere accessibile il dizionario originale o procedere e crearne uno nuovo.


Una soluzione possibile (che non è grande) è quello di rendere la propria classe wrapper per portare il dizionario lungo:

private class DictionaryQueryHolder<TKey, TValue> : IQueryable<KeyValuePair<TKey, TValue>> 
{ 
    public IDictionary<TKey, TValue> Dictionary { get; private set; } 
    private IQueryable<KeyValuePair<TKey, TValue>> Queryable { get; set; } 

    internal DictionaryQueryHolder(IDictionary<TKey, TValue> dictionary) 
    { 
     Dictionary = dictionary; 
     Queryable = dictionary.AsQueryable(); 
    } 

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() 
    { 
     return Queryable.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 

    public Expression Expression 
    { 
     get { return Queryable.Expression; } 
    } 

    public Type ElementType 
    { 
     get { return Queryable.ElementType; } 
    } 

    public IQueryProvider Provider 
    { 
     get { return Queryable.Provider; } 
    } 
} 

Si tratterebbe sia di agire come un wrapper per IQueryable<T> e fornire l'accesso del dizionario al dizionario originale . Ma d'altra parte, chiunque cerchi di recuperare il dizionario dovrebbe sapere quali sono i parametri di tipo generico sono stati (per esempio <string, string>, <int, string>, etc.) al fine di lanciare con successo.

+0

Il suggerimento con il 'campo privato Enumerable' lavorato.Ho anche incluso un fallback nel mio codice ('if (pInfo == null) quindi creare un nuovo dizionario e aggiungere manualmente KeyValuePairs') per mantenere la compatibilità con le versioni future. –

2

Il problema principale è che IQueryable si avvolge intorno al dizionario, piuttosto che essere come IEnumerable <> sopra IDictionary <>, dove si poteva lanciare indietro.

Si può certamente scoprire se il tipo avvolto è un dizionario se si conoscono i tipi coinvolti:

public bool isDictionary<T>(object obj) { 
    return obj.GetType().GenericTypeArguments.Contains(typeof(T)); 
} 

isDictionary<KeyValuePair<string,string>>(dataSource); 

Se non ti dispiace raggiungendo negli oggetti interni, è possibile utilizzare il campo Enumerable privata EnumerableQuery per ottenere una versione di (forse) il dizionario posteriore originale come IEnumerable<>

Ma in realtà convertire da un EnumerableQuery<KeyValuePair<int,int>> nascosto sotto un IQueryable senza fare che penso che avrei dovuto prendere solo il colpo e creare un nuovo dictio da ciò.