2013-03-03 7 views
6

Come un progetto di hobby (e per immergermi più profondamente nei metodi generici/di estensione), sto scrivendo una libreria di controllo dei parametri!In che modo un metodo di estensione può essere associato a una classe generica quando l'argomento type è IEnumerable <T>?

Ho un modello chiamato argomento che descrive un parametro e si presenta così:

public class Argument<T> 
{ 
    internal Argument(string name, T value) 
    { 
     Name = name; 
     Value = value; 
    } 

    public string Name { get; private set; } 

    public T Value { get; private set; } 
} 

Quando la convalida per un parametro inizia, viene creata un'istanza di questo oggetto, e le singole convalide vengono eseguite richiamando i metodi di estensione (che contiene la logica effettiva) che ne appende.

Un tale metodo di estensione verifica che una collezione contiene almeno un elemento, e attualmente si presenta così:

public static Argument<IEnumerable<T>> HasItems<T>(this Argument<IEnumerable<T>> argument) 
{ 
    if (!argument.Value.Any()) 
     throw Error.Generic(argument.Name, "Collection contains no items."); 

    return argument; 
} 

Ma non sembra funzionare. Se fossi, diciamo, di scrivere questo test di unità:

[TestMethod] 
public void TestMethod1() 
{ 
    var argument = new List<int>() { 1, 2, 6, 3, -1, 5, 0 }; 

    Validate.Argument("argument", argument) 
     .IsNotNull() 
     .HasItems() 
     .All(v => v.IsGreaterThan(0)); 
} 

HasItems non si presenta in Intellisense, e ottengo questo errore di compilazione:

'Validation.Argument<System.Collections.Generic.List<int>>' does not contain a definition for 'HasItems' and no extension method 'HasItems' accepting a first argument of type 'Validation.Argument<System.Collections.Generic.List<int>>' could be found (are you missing a using directive or an assembly reference?)

E se cerco passando il valore direttamente nel metodo di estensione, in questo modo:

CollectionTypeExtensions.HasItems(Validate.Argument("argument", argument)); 

ottengo questo:

The best overloaded method match for 'Validation.CollectionTypeExtensions.HasItems<int>(Validation.Argument<System.Collections.Generic.IEnumerable<int>>)' has some invalid arguments

Sulla base della mia ricerca, quello che avevo bisogno per questo di lavoro si chiama "varianza", e vale per le interfacce e delegati, ma non per le classi (es. Tutte le classi sono invarianti)

Quello detto, potrebbe funzionare in un altro modo. Uno che viene in mente è di riscrivere per andare direttamente a T, in questo modo:

public static Argument<T> HasItems<T, TElement>(this Argument<T> argument) 
     where T : IEnumerable<TElement> 
{ 
    if (!argument.Value.Any()) 
     throw Error.Generic(argument.Name, "Collection contains no items."); 

    return argument; 
} 

..Ma che non funziona o perché richiede TElement da specificare in modo esplicito quando il metodo viene richiamato. Potrei anche ricorrere all'utilizzo dell'interfaccia IEnumerable non generica nel vincolo di tipo, ma poi dovrei trovare un modo per forzare l'IEnumerable in IEnumerable (che richiederebbe sapere cosa T è in quel contesto), o duplicare la funzionalità di Any() per verificare l'esistenza di qualsiasi articolo, e c'è un altro metodo di estensione (Tutto) che sarebbe molto, molto disordinato, quindi preferisco evitarlo.

Quindi alla fine, suppongo che la mia domanda sia: come faccio a collegare correttamente il mio metodo di estensione?

risposta

2

Funziona per voi? Sembra un po 'pesante, ma in realtà funziona.

public static Argument<T> HasItems<T>(this Argument<T> argument) where T: IEnumerable 
{ 
    if (!argument.Value.Cast<object>().Any()) 
    { 
     throw Error.Generic(argument.Name, "Collection contains no items."); 
    } 

    return argument; 
} 
+0

Quel ... effettivamente funziona! Quando l'ho guardato con occhi nuovi stamattina, ho finito per seguire una strada diversa, ma segna come risposta perché affronta sinteticamente il problema presentato. Grazie! –

0

Credo che ciò che si vuole realmente è un'interfaccia IArgument che è covariante su T:

public static class Validate 
{ 
    public static IArgument<T> Argument<T>(string name, T value) 
    { 
     return new Argument<T>(name, value); 
    } 
} 

public interface IArgument<out T> 
{ 
    string Name { get; } 
    T Value { get; } 
} 

public class Argument<T> : IArgument<T> 
{ 
    internal Argument(string name, T value) 
    { 
     Name = name; 
     Value = value; 
    } 

    public string Name { get; private set; } 

    public T Value { get; private set; } 
} 

public static class ExtensionMethods 
{ 
    public static IArgument<T> IsNotNull<T>(this IArgument<T> argument) 
    { 
     return argument; 
    } 

    public static IArgument<IEnumerable<T>> HasItems<T>(this IArgument<IEnumerable<T>> argument) 
    { 
     return argument; 
    } 

    public static IArgument<IEnumerable<T>> All<T>(this IArgument<IEnumerable<T>> argument, Predicate<T> predicate) 
    { 
     return argument; 
    } 
} 

[TestMethod] 
public void TestMethod1() 
{ 
    List<int> argument = new List<int>() { 1, 2, 6, 3, -1, 5, 0 }; 

    Validate.Argument("argument", argument) 
     .IsNotNull() 
     .HasItems() 
     .All(v => v > 0); 
}