c# - ¿Cómo puedo implementar NotOfType<T> en LINQ que tenga una buena sintaxis de llamada?
type-inference code-readability (7)
Acabo de intentar esto y funciona ...
public static IEnumerable<TResult> NotOfType<TExclude, TResult>(this IEnumerable<TResult> sequence)
=> sequence.Where(x => !(x is TExclude));
¿Me estoy perdiendo de algo?
Estoy tratando de encontrar una implementación para NotOfType
, que tenga una sintaxis de llamada legible. NotOfType
debería ser el complemento de OfType<T>
y, en consecuencia, produciría todos los elementos que no sean de tipo T
Mi objetivo era implementar un método que se llamaría como OfType<T>
, como en la última línea de este fragmento:
public abstract class Animal {}
public class Monkey : Animal {}
public class Giraffe : Animal {}
public class Lion : Animal {}
var monkey = new Monkey();
var giraffe = new Giraffe();
var lion = new Lion();
IEnumerable<Animal> animals = new Animal[] { monkey, giraffe, lion };
IEnumerable<Animal> fewerAnimals = animals.NotOfType<Giraffe>();
Sin embargo, no puedo encontrar una implementación que admita esa sintaxis de llamada específica.
Esto es lo que he intentado hasta ahora:
public static class EnumerableExtensions
{
public static IEnumerable<T> NotOfType<T>(this IEnumerable<T> sequence, Type type)
{
return sequence.Where(x => x.GetType() != type);
}
public static IEnumerable<T> NotOfType<T, TExclude>(this IEnumerable<T> sequence)
{
return sequence.Where(x => !(x is TExclude));
}
}
Llamar a estos métodos se vería así:
// Animal is inferred
IEnumerable<Animal> fewerAnimals = animals.NotOfType(typeof(Giraffe));
y
// Not all types could be inferred, so I have to state all types explicitly
IEnumerable<Animal> fewerAnimals = animals.NotOfType<Animal, Giraffe>();
Creo que hay grandes inconvenientes con el estilo de estas dos llamadas. El primero sufre de una construcción redundante "de tipo / tipo de", y el segundo simplemente no tiene sentido (¿quiero una lista de animales que no sean animales ni jirafas?).
Entonces, ¿hay una manera de lograr lo que quiero? Si no, ¿podría ser posible en futuras versiones del lenguaje? (Estoy pensando que quizás algún día habremos nombrado argumentos de tipo, o que solo necesitamos proporcionar explícitamente argumentos de tipo que no puedan inferirse.)
¿O simplemente estoy siendo tonto?
Esto puede parecer una sugerencia extraña, pero ¿qué pasa con un método de extensión en el antiguo IEnumerable
? Esto reflejaría la firma de OfType<T>
, y también eliminaría el problema de los parámetros de tipo <T, TExclude>
.
También diría que si ya tiene una secuencia fuertemente NotOfType<T>
, hay muy pocas razones para un NotOfType<T>
especial NotOfType<T>
; Me parece mucho más útil (en mi opinión) excluir un tipo específico de una secuencia de tipo arbitrario ... o permítame ponerlo de esta manera: si está tratando con un IEnumerable<T>
, es trivial llamar Where(x => !(x is T))
; la utilidad de un método como NotOfType<T>
vuelve más cuestionable en este caso.
No estoy seguro de por qué no solo dices:
animals.Where(x => !(x is Giraffe));
Esto me parece perfectamente legible. Es ciertamente más directo para mí que para los animals.NotOfType<Animal, Giraffe>()
que me confundiría si lo descubriera ... el primero nunca me confundiría, ya que es fácil de leer.
Si quisieras una interfaz fluida, supongo que también podrías hacer algo como esto con un método de extensión basado en el Object
:
animals.Where(x => x.NotOfType<Giraffe>())
Qué tal si
animals.NotOf(typeof(Giraffe));
Alternativamente, puede dividir los parámetros genéricos a través de dos métodos :
animals.NotOf().Type<Giraffe>();
public static NotOfHolder<TSource> NotOf<TSource>(this IEnumerable<TSource> source);
public class NotOfHolder<TSource> : IHideObjectMembers {
public IEnumerable<TSource> NotOf<TNot>();
}
Además, debe decidir si también se excluyen los tipos heredados.
Si vas a hacer un método para la inferencia, quieres inferir todo el camino. Eso requiere un ejemplo de cada tipo:
public static class ExtMethods
{
public static IEnumerable<T> NotOfType<T, U>(this IEnumerable<T> source)
{
return source.Where(t => !(t is U));
}
// helper method for type inference by example
public static IEnumerable<T> NotOfSameType<T, U>(
this IEnumerable<T> source,
U example)
{
return source.NotOfType<T, U>();
}
}
llamado por
List<ValueType> items = new List<ValueType>() { 1, 1.0m, 1.0 };
IEnumerable<ValueType> result = items.NotOfSameType(2);
Tuve un problema similar y encontré esta pregunta mientras buscaba una respuesta.
En cambio me conformé con la siguiente sintaxis de llamada:
var fewerAnimals = animals.Except(animals.OfType<Giraffe>());
Tiene la desventaja de que enumera la colección dos veces (por lo que no se puede usar con una serie infinita), pero la ventaja de que no se requiere una nueva función auxiliar, y el significado es claro.
En mi caso de uso real, también terminé agregando un .Where(...)
después del .OfType<Giraffe>()
(las jirafas también se incluyen a menos que cumplan con una condición de exclusión particular que solo tenga sentido para las jirafas)
Usted podría considerar esto
public static IEnumerable NotOfType<TResult>(this IEnumerable source)
{
Type type = typeof(Type);
foreach (var item in source)
{
if (type != item.GetType())
{
yield return item;
}
}
}