1
votes

La covariance en c # n'est pas de type sécurisé. Cette déclaration s'applique bien lorsqu'il s'agit de tableaux, mais pas lorsqu'il s'agit de Ienumerables. Pourquoi?

Cas 1: Covariance dans les tableaux.

public static void Main() {
    BirdCalculations<Bird> bc2 = new BirdCalculations<Pegion>();    //Line 1

    BirdCalculations<Bird> bc1 = new BirdCalculations<Bird>();
    bc1.SetTField(new Pegion());    //Line 2

    //Now, Line 2 makes sense, but what is the problem with Line 1?
}

Cas 2: Covariance dans IEnumerable.

public class Bird { }
public class Pegion : Bird { }

public class BirdCalculations<T>
{
    private T TField;
    public void SetTField(T value)
    {
        this.TField = value;
    }
}

Nous implémentons le même scénario, ie mettre un objet de Array / List de type plus dérivé (string), dans une variable de type moins dérivé (object), puis essayer d'ajouter un élément de type int.

Autre observation connexe: h2>

Considérez que nous avons l'ensemble de classes suivant:

IEnumerable<Object> l1 = new List<string>();
// The following line executes just fine.
l1 = l1.Append(33);

Dans ce scénario, considérez le code suivant:

object[] array = new String[10];
// The following statement produces a run-time exception.  
array[0] = 10;


5 commentaires

Quelle est cette méthode Append () ?


@ haim770 Enumerable.Append - docs.microsoft.com/en-us/dotnet/api/… ...


Append est une méthode d'extension qui ne modifie pas la liste d'origine. Vous discutez de deux scénarios très différents qui ne sont pas liés.


Gaurav, pourriez-vous s'il vous plaît préciser comment ces deux exemples sont liés l'un à l'autre? L'un essaie de modifier le tableau existant et un autre crée une nouvelle séquence à la suite de l'opération Append . Je ne vois pas beaucoup de similitudes entre les deux ...


Ce n'est pas parce que Pegion hérite de Bird que cela ne signifie pas que BirdCalculations hérite de BirdCalculations . Ce ne est pas.


3 Réponses :


1
votes

Si votre classe est l'extrémité réceptrice du paramètre de type T , par exemple ayant la méthode foo (T bar) , vous avez besoin d'une contravariance de T et non d'une covariance, qui est notée dans T code> en C #. Il doit également s'agir d'une interface ou d'un délégué, il n'est donc pas possible d'autoriser l'attribution de l'instance BirdCalculations à la référence BirdCalculations ou vice versa ou autre, peu importe comment T1 et T2 sont liés. Il doit s'agir de IBirdCalculation bc = new BirdCalculation () ou quelque chose comme ça. Voici l'exemple modifié à partir de votre code:

public class Bird { }
public class Pegion : Bird { }

public interface IBirdCalculations<in T> {
    void SetTField(T value);
}

public class BirdCalculations<T> : IBirdCalculations<T>
{
    private T TField;
    public void SetTField(T value)
    {
        this.TField = value;
    }
}

class Program
{
    static void Main() {
        IBirdCalculations<Pegion> bc2 = new BirdCalculations<Bird>();    //Line 1

        IBirdCalculations<Bird> bc1 = new BirdCalculations<Bird>();
        bc1.SetTField(new Pegion());    //Line 2
    }
}

Notez que vous ne pouvez pas attribuer une instance de BirdCalculations à IBirdCalculations code > référence parce que si cela était possible, quelqu'un pourrait invoquer quelque chose comme SetTField (perroquet) et cela ne devrait pas être autorisé.

PS: Les tableaux en C # ne sont pas une variance, c'est d'une manière ou d'une autre historiquement autorisé à être utilisé comme ça, mais cela brise en fait le système de type sécurisé. Ma meilleure hypothèse est que c'était nécessaire alors que nous n'avions pas de générique dans .NET, ce qui est comme il y a 15 ans.


0 commentaires

0
votes

La covariance est sûre pour la lecture, mais pas pour l'écriture. Lorsque vous utilisez un tableau, vous pouvez définir une valeur du tableau sur un type arbitraire - ce n'est pas un type sûr, et c'est le type d'échappement indésirable. De la même manière, la contravariance est sans danger pour l'écriture, mais pas pour la lecture. Malheureusement, il n'y avait aucun moyen de contraindre la variance dans les tableaux lorsque cela a été implémenté, autre que de les rendre entièrement invariants de type - ce qui limiterait sévèrement la réutilisation du code sans génériques (imaginez simplement comment quelque chose comme Array.Sort le ferait travail).

Vous ne pouvez pas modifier un énumérable. Les énumérables sont en lecture seule. Vous n'avez ajouté aucun élément à l'énumérable et vous n'avez rien «modifié» à l'intérieur de cet énumérable. Ce que fait Append , c'est créer un nouvel IEnumerable . Il n'y a pas de violation de sécurité de type. Pour avoir une image mentale, vous pouvez imaginer le nouvel énumérable contenant l'ancien énumérable - mais cela ne change rien. Tous les éléments que les énumérables peuvent renvoyer satisfont toujours à la contrainte d'origine - ils sont tous des objets .


0 commentaires

0
votes

Cela ne fait pas ce que vous pensez que c'est;

        public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T obj)
        {
            foreach (var s in source)
                yield return s;
            yield return obj;
        }

D'abord, vous attribuez une liste à l1, comme vous vous y attendez. Mais alors vous appelez IEnumerable . Append () qui équivaut à;

IEnumerable<Object> l1 = new List<string>();
// The following line executes just fine.
l1 = l1.Append(33);

En d'autres termes, Append crée un nouveau IEnumerable qui peut être utilisé pour visiter chaque élément de votre liste, puis la valeur que vous avez ajoutée.


0 commentaires