2 votes

Le compilateur ne parvient pas à convertir un type générique contraint

J'ai une classe qui a un type générique "G"

Dans mon modèle de classe, j'ai

public class DetailElement : ElementDefinition

Disons que j'ai une méthode comme celle-ci

        public void DoSomething<G>(G generic)
            where G : ElementDefinition
        {
            if (generic is DetailElement)
            {
                ((DetailElement)generic).DescEN = "Hello people"; //line 1
                //////
                ElementDefinition element = generic;
                ((DetailElement)element).DescEN = "Hello again"; //line 3
                //////
                (generic as DetailElement).DescEN = "Howdy"; //line 5
            }
            else
            {
                //do other stuff
            }
        }

Le compilateur signale une erreur à la ligne 1 :

Cannot convert type 'G' to 'DetailElement'

Mais la ligne 3 fonctionne bien. Je peux contourner ce problème en exécutant le code écrit à la ligne 5.

Ce que je voudrais savoir, c'est pourquoi le compilateur signale l'erreur de la ligne 1 et pas celle de la ligne 3, étant donné que, pour autant que je sache, elles sont identiques.

edit : je crains d'avoir oublié un élément important de la logique du cadre.

edit2 : Bien que les solutions à l'erreur du compilateur soient importantes, ma question porte sur la raison pour laquelle le compilateur signale une erreur à la ligne 1 et non à la ligne 3.

7voto

Mark Cidade Points 53945

Si G a été contraint d'être un DetailElement ( where G : DetailElement ), vous pouvez alors aller de l'avant et lancer G à ElementDefinition, c'est-à-dire " (ElementDefinition) generic ". Mais parce que G pourrait être une autre sous-classe de ElementDefinition autres que DetailElement à l'exécution, il ne l'autorisera pas à la compilation si le type est inconnu et invérifiable.

À la ligne 3, le type que vous avez extrait de es connu pour être un ElementDefinition donc tout ce que vous faites, c'est une diffusion ascendante . Le compilateur ne sait pas si le cast sera réussi à l'exécution, mais il vous fait confiance. Le compilateur n'est pas aussi confiant pour les génériques.

En as à la ligne 5 pourrait également renvoyer null et le compilateur ne vérifie pas statiquement le type pour voir s'il est sûr dans ce cas. Vous pouvez utiliser as con tous et pas seulement ceux qui sont compatibles avec les ElementDefinition .

De _Est-il possible d'effectuer une conversion vers et depuis des paramètres de type générique ?_ sur MSDN :

Le compilateur ne vous laissera qu'implicitement couler les paramètres de type générique en objets ou en types spécifiés par des contraintes.

Un tel casting implicite est bien sûr sûr sûr du point de vue du type, car toute incompatibilité est découverte au moment de la compilation.

Le compilateur vous permet d'attribuer explicitement des paramètres de type générique à n'importe quelle interface, mais pas à une classe :

   interface ISomeInterface {...}
   class SomeClass {...}
   class MyClass<T> 
    {
      void SomeMethod(T t)
       {
         ISomeInterface obj1 = (ISomeInterface)t;//Compiles
         SomeClass      obj2 = (SomeClass)t;     //Does not compile
       }
    }

Toutefois, vous pouvez forcer une conversion d'un paramètre de type générique en un autre type à l'aide d'une variable objet temporaire

 void SomeMethod<T>(T t) 
  { object temp = t;
    MyOtherClass obj = (MyOtherClass)temp;  
  }

Il va sans dire qu'une telle distribution explicite est dangereuse car elle peut provoquer une exception à l'exécution si le type concret utilisé à la place du paramètre de type générique ne dérive pas du type vers lequel vous avez explicitement effectué la distribution.

Au lieu de risquer une exception de casting, il est préférable d'utiliser la fonction is o as des opérateurs. Les is renvoie un message vrai si le paramètre de type générique est du type demandé, et as effectuera une conversion si les types sont compatibles, et renverra null dans le cas contraire.

public void SomeMethod(T t)
 {
   if(t is int) {...}

   string str = t as string;
   if(str != null) {...}
 }

1voto

benjismith Points 8739

Votre clause where ne devrait-elle pas être "where G : DetailElement" ?

Dans le code que vous avez écrit, un DetailElement est un ElementDefinition, mais un ElementDefinition n'est pas nécessairement un DetailElement. La conversion implicite est donc illégale.

Existe-t-il d'autres types de ElementDefinition que vous pourriez passer dans cette méthode ? Si c'est le cas, une exception sera levée lorsque vous tenterez de les convertir en instances de DetailElement.

EDIT :

Maintenant que vous avez modifié votre liste de codes, je vois que vous vérifiez le type pour vous assurer qu'il s'agit bien d'un DetailElement avant d'entrer dans ce bloc de code. Malheureusement, le fait est que vous ne pouvez pas effectuer de downcast implicite, même si vous avez déjà vérifié les types vous-même. Je pense que vous devriez vraiment utiliser le mot-clé "as" au début de votre bloc :

DetailElement detail = generic as DetailElement;
if (detail == null) {
   // process other types of ElementDefinition
} else {
   // process DetailElement objects
}

Mieux encore, pourquoi ne pas utiliser le polymorphisme pour permettre à chaque type de ElementDefinition de définir sa propre méthode DoSomething, et laisser le CLR s'occuper de la vérification des types et de l'invocation des méthodes pour vous ?

1voto

Michael Meadows Points 15277

En règle générale, l'upcasting est un code smell. Vous pouvez l'éviter en surchargeant les méthodes. Essayez ceci :

public void DoSomething(DetailElement detailElement)
{
    // do DetailElement specific stuff
}

public void DoSomething<G>(G elementDefinition)
    where G : ElementDefinition
{
    // do generic ElementDefinition stuff
}

Vous pouvez alors tirer parti de la surcharge des méthodes en utilisant ce code :

DetailElement foo = new DetailElement();

DoSomething(foo); // calls the non-generic method
DoSomething((ElementDefinition) foo); // calls the generic method

0voto

Guvante Points 10338

Cela entraînera un peu plus de code si vous avez beaucoup d'ElementDefinitions qui vous préoccupent, mais c'est probablement la solution la plus simple que vous puissiez obtenir qui n'implique pas d'absurdité.

    public void DoSomething<G>(G generic)
        where G : ElementDefinition
    {
        DetailElement detail = generic as DetailElement;
        if (detail != null)
        {
            detail.DescEN = "Hello people";
        }
        else
        {
            //do other stuff
        }
    }

Une autre solution possible que j'ai utilisée lorsque j'avais besoin de ces informations, en loo d'une variable objet temporaire.

DetailElement detail = (DetailElement)(object)generic;

Cela fonctionne, mais la forme as est probablement la meilleure.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X