Nonostante io davvero non mi piace questo approccio, lo so che è possibile fornire un set di strumenti simili a LINQ to Objects che è richiamabile direttamente su un'interfaccia IEnumerable
, senza forzare un cast di IEnumerable<object>
(cattivo: possibile boxe!) e senza casting a IEnumerable<TFoo>
(anche peggio: avremmo bisogno di sapere e scrivere TFoo!).
Tuttavia, è:
- non libero per il tempo di esecuzione: si può essere pesante, non ho eseguito test di performance
- non libero per gli sviluppatori: hai veramente bisogno di scrivere tutti coloro LINQ -come i metodi di estensione per IEnumerable (o trovare una lib che lo faccia)
- non semplice: è necessario ispezionare attentamente il tipo in entrata e fare attenzione con molte possibili opzioni
- non è un oracolo: data una raccolta che strumento s IEnumerable ma non implementa
IEnumerable<T>
solo può gettare errore o silenziosamente gettarlo ai IEnumerable<object>
- non sempre funziona: data una collezione che implementa sia
IEnumerable<int>
e IEnumerable<string>
semplicemente non può sapere che cosa fare; anche rinunciare e colata a IEnumerable<object>
non suona proprio qui
Ecco un esempio per .Net4 +:
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
public static void Main()
{
Console.WriteLine("List<int>");
new List<int> { 1, 2, 3 }
.DoSomething()
.DoSomething();
Console.WriteLine("List<string>");
new List<string> { "a", "b", "c" }
.DoSomething()
.DoSomething();
Console.WriteLine("int[]");
new int[] { 1, 2, 3 }
.DoSomething()
.DoSomething();
Console.WriteLine("string[]");
new string[] { "a", "b", "c" }
.DoSomething()
.DoSomething();
Console.WriteLine("nongeneric collection with ints");
var stack = new System.Collections.Stack();
stack.Push(1);
stack.Push(2);
stack.Push(3);
stack
.DoSomething()
.DoSomething();
Console.WriteLine("nongeneric collection with mixed items");
new System.Collections.ArrayList { 1, "a", null }
.DoSomething()
.DoSomething();
Console.WriteLine("nongeneric collection with .. bits");
new System.Collections.BitArray(0x6D)
.DoSomething()
.DoSomething();
}
}
public static class MyGenericUtils
{
public static System.Collections.IEnumerable DoSomething(this System.Collections.IEnumerable items)
{
// check the REAL type of incoming collection
// if it implements IEnumerable<T>, we're lucky!
// we can unwrap it
// ...usually. How to unwrap it if it implements it multiple times?!
var ietype = items.GetType().FindInterfaces((t, args) =>
t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>),
null).SingleOrDefault();
if (ietype != null)
{
return
doSomething_X(
doSomething_X((dynamic)items)
);
// .doSomething_X() - and since the compile-time type is 'dynamic' I cannot chain
// .doSomething_X() - it in normal way (despite the fact it would actually compile well)
// `dynamic` doesn't resolve extension methods!
// it would look for doSomething_X inside the returned object
// ..but that's just INTERNAL implementation. For the user
// on the outside it's chainable
}
else
// uh-oh. no what? it can be array, it can be a non-generic collection
// like System.Collections.Hashtable .. but..
// from the type-definition point of view it means it holds any
// OBJECTs inside, even mixed types, and it exposes them as IEnumerable
// which returns them as OBJECTs, so..
return items.Cast<object>()
.doSomething_X()
.doSomething_X();
}
private static IEnumerable<T> doSomething_X<T>(this IEnumerable<T> valitems)
{
// do-whatever,let's just see it being called
Console.WriteLine("I got <{1}>: {0}", valitems.Count(), typeof(T));
return valitems;
}
}
Sì, è stupido. Li ho incatenati quattro volte (2outsidex2inside) solo per mostrare che le informazioni sul tipo sono non perse nelle chiamate successive. Il punto era dimostrare che il "punto di ingresso" prendesse un valore non generico IEnumerable
e che <T>
sia risolto ovunque possa essere. È possibile adattare facilmente il codice per renderlo un normale metodo LINQ-to-Objects .Count()
. Allo stesso modo, si possono scrivere anche tutte le altre operazioni.
Questo esempio utilizza dynamic
per consentire alla piattaforma di risolvere la T più stretta per IEnumerable, se possibile (che è necessario garantire per prima). Senza dynamic
(ovvero .Net2.0) avremmo bisogno di invocare il dosomething_X
tramite riflessione, o implementarlo due volte come dosomething_refs<T>():where T:class
+ dosomething_vals<T>():where T:struct
e fare un po 'di magia per chiamarlo correttamente senza effettivamente il casting (probabilmente il riflesso, di nuovo).
Tuttavia, sembra che si possa ottenere qualcosa-come-linq lavorando "direttamente" su cose nascoste dietro IEnumerable non generico. Tutto grazie al fatto che gli oggetti che si nascondono dietro a IEnumerable hanno ancora le proprie informazioni complete sul tipo (sì, questa ipotesi potrebbe non funzionare con COM o Remoting). Tuttavia .. Penso che accontentarsi di IEnumerable<T>
è un'opzione migliore. Lasciamo semplicemente il vecchio IEnumerable
in casi speciali in cui non c'è davvero altra opzione.
..oh .. e io in realtà non ha verificato se il codice di cui sopra è corretto, veloce, sicuro, risparmio delle risorse, pigro-valutazione, ecc
Vuoi davvero dire '' IEnumerable
@Maarten Sì, intendo usare 'IEnumerable', perché devo gestire il caso generale e supportare diversi tipi di elementi. –
Lingxi
Perché dovresti lavorare con 'IEnumerable' in primo luogo? Perché no 'IEnumerable range = new [] {1, 2, 3};' invece? - Btw. 'IEnumerable' * fa * box, giusto? Se si 'foreach' su un (non generico)' IEnumerable', si ottiene un 'oggetto'. A * generic * enumerable ('IEnumerable ') d'altra parte fa * not * box. –
Corak