web-dev-qa-db-fra.com

C # Casting une liste <ObjBase> en tant que liste <Obj>

Pourquoi ne puis-je pas convertir un List<ObjBase> en List<Obj>? Pourquoi ce qui suit ne fonctionne pas:

internal class ObjBase
   {
   }

internal class Obj : ObjBase
   {
   }   

internal class ObjManager
{
    internal List<Obj> returnStuff()
    {
       return getSomeStuff() as List<Obj>;
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }

}

Au lieu de cela, je dois faire ceci:

internal class ObjBase
   {
   }

internal class Obj : ObjBase
   {
   }

internal class ObjManager
{
    internal List<Obj> returnStuff()
    {
       List<ObjBase> returnedList = getSomeStuff();
       List<Obj> listToReturn = new List<Obj>(returnedList.Count);
       foreach (ObjBase currentBaseObject in returnedList)
       {
          listToReturn.Add(currentBaseObject as Obj);
       }
       return listToReturn;
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }
}

J'obtiens l'erreur suivante dans Visual Studio 2008 (raccourci pour la lisibilité):

Impossible de convertir le type 'List' en 'List' via une conversion de référence, une conversion de boxe, une conversion de unboxing, une conversion d'encapsulation ou une conversion de type null

Merci.

26
AndrewJacksonZA
9
gjutras

Vous pouvez utiliser les méthodes d'extension Cast et ToList à partir de System.Linq pour les inclure sur une ligne.

Au lieu de 

internal List<Obj> returnStuff()
{
   return getSomeStuff() as List<Obj>;
}

faire ceci:

internal List<Obj> returnStuff()
{
   return getSomeStuff().Cast<Obj>().ToList();
}
34
RaYell

Je peux seulement décrire le "problème" à partir d'une vue Java, mais de ce que je sais peu, cet aspect est le même en C # et en Java:

Un List<ObjBase> n'est pas un List<Obj>, car il pourrait contenir un objet ObjBase qui n'est pas un objet Obj.

L’inverse d’un List<Obj> peut pas être converti en un List<ObjBase> car le premier garantit l’acceptation d’un appel Add() avec un argument ObjBase, que le dernier n’acceptera pas!

Donc, pour résumer: même si une Obj est-une ObjBase un List<Obj> est pas a List<ObjBase>.

10
Joachim Sauer
3
Dewfy

Je pense que vous comprenez mal le casting que vous essayez de faire. Vous pensez que vous modifiez le type de l'objet stocké dans la liste, alors que vous essayez de modifier le type de la liste elle-même. Il est plutôt logique que vous ne puissiez pas modifier la liste telle que vous l'avez déjà remplie.

Vous pouvez la voir comme une liste d'une classe de base, puis la lancer lorsque vous traitez les éléments de la liste. Ce serait mon approche.

Quel est le but de cette tentative de casting?

1
Lazarus

C # ne prend actuellement pas en charge la variance pour les types génériques. D'après ce que j'ai lu, cela va changer en 4.0.

Voir ici pour plus d’informations sur la variance des génériques.

1
Scott Ivey

list.ConvertAll semble tentant, mais a un gros inconvénient: il va créer une toute nouvelle liste. Cela affectera les performances et l'utilisation de la mémoire, en particulier pour les grandes listes.

Avec un peu plus d'effort, vous pouvez créer une classe de liste wrapper qui conserve la liste d'origine en tant que référence interne et convertit les éléments uniquement lorsqu'ils sont utilisés.

Usage:

var x = new List<ObjBase>();
var y = x.CastList<ObjBase, Obj>(); // y is now an IList<Obj>

Code à ajouter à votre bibliothèque:

public static class Extensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

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

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}
1
Bigjim

Linq a une méthode ConvertAll. donc quelque chose comme 

list.ConvertAll<Obj>(objBase => objbase.ConvertTo(obj));

Je ne sais pas quoi suggérer d'autre. Je suppose que ObjBase est la classe de base et si tous les objets ObjBase sont des objets Obj, je ne suis pas sûr de la raison pour laquelle vous auriez les deux objets en premier lieu. Peut-être que je suis hors de propos.

Edit: la méthode list.Cast fonctionnerait mieux que la méthode ci-dessus, en supposant qu’elles soient transposables les unes aux autres. J'ai oublié ça jusqu'à ce que je lise les autres réponses.

0
Jimmeh

Lazarus :
Je pensais que le compilateur se rendrait compte que je voulais que des actions soient effectuées sur les objets de la liste et non que j'essayais de lancer la liste elle-même.

Quelques informations supplémentaires:

public abstract class ObjBase
   {
   }

internal interface IDatabaseObject
   {
   }

public class Obj : ObjBase, IDatabaseObject
   {
   }


internal interface IDatabaseObjectManager
   {
      List<ObjBase> getSomeStuff();
   }

public class ObjManager : IObjManager
{
    public List<Obj> returnStuff()
    {
       return getSomeStuff().Cast <Customer>().ToList<Customer>();
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }
}

Maintenant, le code client en dehors de cette DLL peut aller: ObjManager objM = new ObjManager (); List listOB = objM.returnStuff (); Je vais créer plusieurs types Obj et ObjManager pour cette partie (O/RM) de l'application.

(Darn bloc de commentaires à court de caractères! :-)

0
AndrewJacksonZA

C'est une douleur majeure en C # - c'est comment les génériques ont été conçus. La liste n’étend pas la liste, c’est un type complètement différent. Vous ne pouvez en aucun cas les attribuer ou les attribuer, votre seule option est de copier une liste sur une autre.

0
Grzenio

Voici comment j'ai corrigé la conversion d'un 

list<SomeOtherObject>

à un

object

et ensuite à un

List<object>

https://stackoverflow.com/a/16147909/2307326

0
Andrew Marais