web-dev-qa-db-fra.com

retourne la liste générique inconnue <T>

et merci pour toute aide. 

Comment renvoyer d'une méthode un type Generic.List inconnu. 

public void Main()
{
  List<A> a= GetData("A");   
}

public List<T> GetData(string listType)
{
   if(listType == "A")
   {
     List<A> a= new List<A>() 
     ...
     return a; 
   }
   else
   {
     List<B> b = new List<B>()
     return b;

   }
}

Dans l'exemple ci-dessous, je reçois une erreur similaire à: Impossible de convertir List<A> en List<T>

Est-ce possible? L'erreur se produit sur le 'return a;' ligne de code.
Aussi, que dois-je faire pour m'assurer qu'aucune erreur ne se produit sur la ligne: 

List<A> a= GetData("A");   

Merci, Steven 

18
stevenrosscampbell

Utilisez IList au lieu de List<T>.

23
John Rasch

Une alternative à la restitution d'une liste d'objets serait de s'assurer que A et B dérivent d'un type de base commun ou d'implémenter une interface commune, puis de renvoyer une liste de ce type de base ou de cette interface. Incluez une contrainte sur la méthode générique à cet effet: -

List<ICommon> GetData<T>() where T: ICommon
{

}
12
AnthonyWJones

Vous ne pouvez pas retourner directement un List<T> comme celui-ci. 

Pourquoi? Fondamentalement, parce que List<A> et List<B> (ou List<string> vs List<int> qui est la même chose) sont considérés comme 2 classes totalement distinctes sans rapport.
Tout comme vous ne pouvez pas renvoyer un string à partir d’une fonction déclarée comme retournant int, vous ne pouvez pas renvoyer une liste de chaînes d’une fonction déclarée à renvoyer une liste d’ints. Le <T> est un peu un piège rouge. Vous ne pouvez pas écrire une méthode générique qui retourne à la fois des chaînes et des ints ...

Voir ici pour plus d’informations sur ce genre de chose.

Donc, ce que vous devez faire, c'est renvoyer quelque chose dont les deux types dérivent (ce qu'ils "ont en commun").
Comme John Rasch dit , vous pouvez renvoyer IList, (notez le nom générique, il s’agit donc d’une liste de objects) ou simplement le renvoyer sous la forme object. Malheureusement, il n’existe aucun moyen de conserver le type de la liste.

9
Orion Edwards

À moins d'une raison spécifique pour laquelle vous ne pouvez pas spécifier le type actuel à l'avance, vous pouvez simplement rendre la méthode générique elle-même:

public void Main() {
    List<A> a = GetData<A>();
}

public List<TType> GetData<TType>() {
     List<TType> list= new List<TType>();
     ...
     return list; 
}
7
Kevin Kibler

EDIT selon la réponse d'Orion ci-dessous, en ajoutant une suggestion suggérée par AnthonyWJones.

vous devriez probablement avoir une classe/interface abstraite dont A et B héritent de

    public interface IMyInterface { }
    public class A : IMyInterface { }
    public class B : IMyInterface { }

    public List<IMyInterface> GetData<T>() where T : IMyInterface
    {
        List<IMyInterface> myList = new List<IMyInterface>();
        if (typeof(T) == typeof(A))
        {
            myList.Add(new A());
        }
        if (typeof(T) == typeof(B))
        {
            myList.Add(new B());
        }
        return myList;
    }
5
Jon Erickson

J'ai eu à résoudre un problème similaire récemment où aucune des solutions proposées n'était satisfaisante; contraindre le paramètre type n'était pas pratique. Au lieu de cela, je laisse les utilisateurs de la méthode décider de la manière de rassembler les données . Par exemple, vous pouvez écrire une version générique de String.Split () qui renvoie une liste fortement typée, à condition que vous lui indiquiez comment convertir des sous-chaînes en T.

Une fois que vous êtes prêt à transférer des responsabilités dans la pile d'appels (et à vous sentir à l'aise de passer de lambdas), vous pouvez généraliser ce modèle de manière arbitraire. Par exemple, si la manière dont GetData () varie (comme le supposent apparemment certaines réponses), vous pouvez également placer cette fonction dans la portée de l'appelant.

Démo:

static void Main(string[] args)
{
    var parseMe = "Hello world!  1, 2, 3, DEADBEEF";

    // Don't need to write a fully generic Process() method just to parse strings -- you could 
    // combine the Split & Convert into one method and eliminate 2/3 of the type parameters
    List<string> sentences = parseMe.Split('!', str => str);
    List<int> numbers = sentences[1].Split(',', str => Int32.Parse(str, NumberStyles.AllowHexSpecifier | NumberStyles.AllowLeadingWhite));

    // Something a little more interesting
    var lettersPerSentence = Process(sentences,
                                     sList => from s in sList select s.ToCharArray(),
                                     chars => chars.Count(c => Char.IsLetter(c)));
}

static List<T> Split<T>(this string str, char separator, Func<string, T> Convert)
{       
    return Process(str, s => s.Split(separator), Convert).ToList();
}

static IEnumerable<TOutput> Process<TInput, TData, TOutput>(TInput input, Func<TInput, IEnumerable<TData>> GetData, Func<TData, TOutput> Convert)
{
    return from datum in GetData(input)
           select Convert(datum);
}

Les gourous de la programmation fonctionnelle bâilleront probablement à cette exploration: "vous ne faites que composer Map quelques fois". Même les gars de C++ peuvent prétendre que c’est un exemple où les techniques de template (c'est-à-dire STL transform () + foncteurs) nécessitent moins de travail que les génériques. Mais comme c’est principalement le C #, il était agréable de trouver une solution préservant à la fois la sécurité des types et l’utilisation d’un langage idiomatique.

2
Richard Berg

Si vous ne connaissez pas le type souhaité jusqu'au moment de l'exécution, les génériques sont probablement le mauvais outil pour le travail.

Si votre fonction change de manière significative le comportement (par exemple, le type de retour) en fonction d'un argument, il devrait probablement s'agir de deux fonctions.

Il semble que cette fonction ne devrait pas être générique, mais plutôt deux fonctions.

public void Main() {
    List<A> a = GetDataA();
}

public List<A> GetDataA() {
     List<A> a= new List<A>() 
     ...
     return a; 
}
public List<B> GetDataB() {
     List<B> b= new List<B>() 
     ...
     return b; 
}
1
Craig Gidney

Vous pourriez faire quelque chose comme:

public void Main()
{
    List<int> a = GetData<int>();
    List<string> b = GetData<string>();
}

public List<T> GetData<T>()
{
    var type = typeof(T);
    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        type = type.GenericTypeArguments[0];
    }

    if (type == typeof(int))
    {
        var a = new List<int> { 1, 2, 3 };
        return a.Select(v => v != null ? (T)Convert.ChangeType(v, type) : default(T)).ToList();
    }
    else if (type == typeof(string))
    {
        var b = new List<string> { "a", "b", "c" };
        return b.Select(v => v != null ? (T)Convert.ChangeType(v, type) : default(T)).ToList();
    }
}

Peut-être que vous pouvez adapter cela à vos besoins.

1
Marc Selman

Une solution consiste à encapsuler les données dans un conteneur qui fonctionnera en tant que client dans le modèle de visiteur .

D'abord une interface correspondant au motif:

/// <summary>
/// The Client
/// </summary>
interface IDataContainer
{
    void AcceptDataProcessor(IDataProcessor dataProcessor);
}

/// <summary>
/// The Visitor.
/// </summary>
interface IDataProcessor
{
    void WorkOn<TData>(List<TData> data);
}

Puis une implémentation de chacun:

class DataContainer<TData> : IDataContainer
{
    readonly List<TData> list;

    public DataContainer(List<TData> list)
    {
        this.list = list;
    }

    public void AcceptDataProcessor(IDataProcessor dataProcessor)
    {
        dataProcessor.WorkOn(list); // Here the type is known.
    }
}

class PrintDataProcessor : IDataProcessor
{
    public void WorkOn<TData>(List<TData> data)
    {
        // print typed data.
    }
}

Puis une utilisation de celui-ci:

public void Main()
{
    var aContainer = GetData("A");
    var bContainer = GetData("B");

    var printProccessor = new PrintDataProcessor();

    aContainer.AcceptDataProcessor(printProccessor); // Will print A data
    bContainer.AcceptDataProcessor(printProccessor); // Will print B data
}


public IDataContainer GetData(string listType)
{
    if (listType == "A")
        return new DataContainer<A>(new List<A>());
    if (listType == "B")
        return new DataContainer<B>(new List<B>());
    throw new InvalidOperationException();
}

L'idée est que DataContainer connaisse le type sous-jacent mais ne l'expose pas.

  • Il ne l'expose pas, donc GetData peut contenir tout type de données (mais elles sont masquées).
  • DataContainer connais le type sous-jacent, il est en charge d'appeler la méthode bien typée du travailleur: dataProcessor.WorkOn(list);

C'est un modèle puissant mais qui coûte beaucoup en termes de code.

0
Orace

Je sais que c'est trop tard, mais je suis venu ici avec le même problème et c'est comme ça que j'ai travaillé avec des interfaces. Je pensais le poster pour le bénéfice des autres

 public interface IEntity
    {
        int ID
        {
            get;
            set;
        }
    }

public class Entity2:IEntity
    {
        public string Property2;

        public int ID
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    }

De même pour Entity1.

Maintenant, dans ma classe (ma couche métier), j'ai cette méthode

 public List<IEntity> GetEntities(Common.EntityType entityType)
           {
               List<IEntity> entities = new List<IEntity>();

               switch (entityType)
               {
                   case Common.EntityType.Accounts:
                       Entity1 entity1 = new Entity1();
                       entity1.Property1 = "AA";
                       entities.Add(entity1);

                       break;
                   case Common.EntityType.Brands:
                       Entity2 entity2 = new Entity2();
                       entity2.Property2 = "AA";
                       entities.Add(entity2);

                       break;
                   default:
                       break;
               }

 return entities;
       }

De l'interface utilisateur, je l'appellerais comme ça

BusinessClass b = new BusinessClass();
        List<IEntity> a = b.GetEntities(Common.EntityType.Accounts);

J'espère que cela t'aides

0
hangar18