web-dev-qa-db-fra.com

Méthode générique multiple (OR) type contrainte

En lisant this , j'ai appris qu'il était possible de permettre à une méthode d'accepter des paramètres de plusieurs types en en faisant une méthode générique. Dans l'exemple, le code suivant est utilisé avec une contrainte de type pour garantir que "U" est un IEnumerable<T>.

public T DoSomething<U, T>(U arg) where U : IEnumerable<T>
{
    return arg.First();
}

J'ai trouvé du code supplémentaire qui permettait d'ajouter plusieurs contraintes de type, telles que:

public void test<T>(string a, T arg) where T: ParentClass, ChildClass 
{
    //do something
}

Cependant, ce code semble indiquer que arg doit être à la fois un type de ParentClass et ChildClass. Ce que je veux faire, c'est dire que arg pourrait être un type de ParentClass ou ChildClass de la manière suivante:

public void test<T>(string a, T arg) where T: string OR Exception
{
//do something
}

Votre aide est appréciée comme toujours!

114
Mansfield

Ce n'est pas possible. Vous pouvez toutefois définir des surcharges pour des types spécifiques:

public void test(string a, string arg);
public void test(string a, Exception arg);

Si ceux-ci font partie d'une classe générique, ils seront préférés à la version générique de la méthode.

56
Botz3000

La réponse de Botz est correcte à 100%, voici une courte explication:

Lorsque vous écrivez une méthode (générique ou non) et que vous déclarez les types de paramètres pris par la méthode, vous définissez un contrat:

Si vous me donnez un objet qui sait comment faire l'ensemble des choses que Type T sait comment faire, je peux livrer soit 'a': une valeur de retour du type que je déclare, ou 'b': une sorte de comportement qui utilise ce type.

Si vous essayez de lui attribuer plusieurs types à la fois (en utilisant un ou) ou essayez de le renvoyer pour renvoyer une valeur pouvant contenir plusieurs types, le contrat devient flou:

Si vous me donnez un objet qui sait sauter à la corde ou sait calculer le nombre de pi au 15ème chiffre, je retournerai soit un objet qui peut aller à la pêche, soit peut-être mélanger du béton.

Le problème est que lorsque vous entrez dans la méthode, vous n'avez aucune idée s'ils vous ont donné un IJumpRope ou un PiFactory. En outre, lorsque vous utilisez la méthode (en supposant que vous l’ayez compilée comme par magie), vous n'êtes pas vraiment sûr de savoir si vous avez un Fisher ou un AbstractConcreteMixer. Fondamentalement, cela rend le tout plus confus.

La solution à votre problème est l’une des deux possibilités suivantes:

  1. Définissez plus d'une méthode qui définit chaque transformation possible, chaque comportement ou autre. C'est la réponse de Botz. Dans le monde de la programmation, on parle de surcharge de la méthode.

  2. Définissez une classe ou une interface de base qui sait comment faire tout ce dont vous avez besoin pour la méthode et dont une seule méthode prend juste ce type. Cela peut impliquer de placer string et Exception dans une petite classe pour définir comment vous prévoyez de les mapper vers la mise en œuvre, mais tout est extrêmement clair et facile à lire. Je pourrais venir, dans quatre ans, lire votre code et comprendre facilement ce qui se passe.

Le choix que vous choisissez dépend de la complexité du choix 1 et 2 et de son extension.

Donc, pour votre situation spécifique, je vais imaginer que vous sortez juste un message ou quelque chose de l'exception:

public interface IHasMessage
{
    string GetMessage();
}

public void test(string a, IHasMessage arg)
{
    //Use message
}

Maintenant, tout ce dont vous avez besoin sont des méthodes qui transforment un string et un Exception en un IHasMessage. Très facile.

24
Crisfole

Si ChildClass signifie qu'il est dérivé de ParentClass, vous pouvez simplement écrire ce qui suit pour accepter ParentClass et ChildClass;

public void test<T>(string a, T arg) where T: ParentClass 
{
    //do something
}

D'autre part, si vous souhaitez utiliser deux types différents sans relation d'héritage entre eux, vous devez envisager les types implémentant la même interface.

public interface ICommonInterface
{
    string SomeCommonProperty { get; set; }
}

public class AA : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;set;
    }
}

public class BB : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;
        set;
    }
}

alors vous pouvez écrire votre fonction générique comme;

public void Test<T>(string a, T arg) where T : ICommonInterface
{
    //do something
}
7
daryal