web-dev-qa-db-fra.com

Laissez la méthode prendre n'importe quel type de données en c #

J'ai beaucoup de tests unitaires qui testent à peu près le même comportement. Cependant, le type de données change.

J'essaie de créer une méthode générique qui peut prendre n'importe quel type de données. J'ai essayé de faire mon paramètre d'entrée var mais ce n'est pas autorisé. Aussi, regardé dans les génériques c # mais qui traite généralement d'une liste.

27
socialMatrix

Vous pouvez faire du paramètre un object:

public void DoSomething(object arg)
{
   //...

Ou vous pouvez faire ce que je préfère et créer une méthode générique:

public void DoSomething<T>(T arg)
{
    //...

L'approche générique présente deux avantages majeurs, et je vais vous donner des exemples de leur utilité:

  1. Même si vous ne spécifiez pas explicitement le type de arg, vous y avez toujours accès.
  2. Vous pouvez ajouter des contraintes sur les types que vous souhaitez autoriser.

Inversement, l'approche object présente certains inconvénients importants:

  1. Puisque vous traitez arg comme un object, vous ne pourrez faire que ce que vous pourriez faire avec n'importe objet.
  2. Si vous passez un type de valeur en tant que paramètre object, la variable sera encadré , ce qui signifie un impact sur les performances. Ce n'est pas un énorme succès, mais si vous appelez DoSomething plusieurs milliers de fois de suite, vous pourriez commencer à le ressentir.

Génériques et contraintes de type

L'ajout d'une contrainte de type à une méthode générique vous permet de restreindre la méthode afin qu'elle n'accepte que certains types. Pourquoi est-ce utile? Parce que même si vous ne savez pas - ou ne vous souciez pas - du type spécifique avec lequel vous travaillez, vous en savez maintenant quelque chose et vous pouvez utiliser ces informations.

Considérez la configuration suivante:

public interface IAnimal 
{ 
    void Move(); 
}
public class Duck : IAnimal
{
    public void Move() 
    { 
        Console.WriteLine("Flying"); 
    }
}
public class Fish : IAnimal
{
    public void Move()
    { 
        Console.WriteLine("Swimming"); 
    }
}
public class Ant : IAnimal
{
    public void Move()
    { 
        Console.WriteLine("Walking"); 
    }
}    

Puisque nous avons une interface IAnimal, nous pouvons écrire des méthodes génériques ciblant toute implémentation de IAnimal:

public class Program
{
    static void DoMove<T>(T animal) where T : IAnimal
    {
        animal.Move();
    }
    public static void Main(string[] args)
    {            
        Duck duck = new Duck(); 
        Fish fish = new Fish();
        Ant ant = new Ant(); 

        DoMove<Duck>(duck);
        DoMove<Fish>(fish);
        DoMove<Ant>(ant);
    }
}

Exécutez-le: http://rextester.com/GOF1761

Lorsque nous écrivons la méthode DoMove, peu nous importe que son paramètre animal soit un Duck, un Fish, un Ant ou autre chose. Tout ce qui nous intéresse, c'est d'appeler animal.Move(). Puisque nous avons utilisé la contrainte where T : IAnimal, Le compilateur sait tout ce que nous avons besoin de savoir:

  1. La variable animal est de type T.
  2. Quel que soit T, il implémente IAnimal.
  3. Tout ce qui implémente IAnimal a une méthode Move().
  4. Par conséquent, nous pouvons appeler en toute sécurité animal.Move().

(Soit dit en passant, oui, nous pourrions simplement écrire DoMove en tant que static void DoMove(IAnimal animal), mais c'est une autre discussion.)

Inférence de type (et certaines de ses implications)

Très bien, mais allons plus loin. Dans de nombreux cas, vous pouvez appeler des méthodes génériques sans avoir à spécifier leurs paramètres de type. Cela s'appelle inférence de type , et en plus de vous éviter de taper, cela peut être utile lorsque vous effectuez la même opération sur des objets de types différents.

public static void Main(string[] args)
{            
    IAnimal[] animals = new IAnimal[] 
    {
        new Duck(),
        new Fish(),
        new Ant()
    };

    foreach (IAnimal animal in animals)
    {
        DoMove(animal);
    }
}

Exécutez-le: http://rextester.com/OVKIA12317

Vous n'avez qu'à écrire la méthode DoMove<T> Une seule fois, et vous pouvez l'appeler sur n'importe quel type de IAnimal sans avoir à donner un type plus spécifique. La version appropriée de Move sera appelée à chaque fois, car DoMove<T> Est en mesure de déduire le type à utiliser pour T. Lorsque vous appelez DoMove(duck), .NET comprend que vous voulez vraiment dire DoMove<Duck>(duck), qui appelle ensuite la méthode Move sur la classe Duck.

51
Justin Morgan

Vous pouvez prendre object comme type de paramètre. Encore mieux, peut-être, serait d'utiliser des génériques:

void MyMethod<T>(T parm) { ... }

De cette façon, le paramètre est en fait du type que l'utilisateur a transmis - il n'est pas encadré comme avec object et les types de valeur.

8
Kirk Woll
void MyTestMethod<T>(T t) { }

vous donne une méthode de test générique, mais je ne peux imaginer aucun moyen qui pourrait être utile. De quoi avez-vous besoin pour tester? Comment savez-vous que le type T possède ces méthodes? T peut être de type any dans la méthode ci-dessus. Les seules méthodes que vous pouvez appeler à partir de t dans l'exemple ci-dessus sont les méthodes courantes de object.

Ce que vous devez vraiment faire, c'est identifier un comportement commun par rapport à un ou plusieurs types que vous souhaitez tester, et définir le contrat syntaxique de ce comportement via une interface. Vous pouvez ensuite contraindre votre méthode de test générique à n'accepter que les types qui implémentent cette interface.

interface IMyInterface
{
    void DoSomething();
} 

void MyTestMethod<T>(T t) where T : IMyInterface
{ 
    t.DoSomething();
}
4
public void YourMethod<T>(T parameter)
{
}
3
Aducci

essayez d'utiliser un mot clé dynamique, cela fonctionnera à condition que tous vos différents types aient les mêmes méthodes que celles utilisées par vos tests unitaires, sinon vous obtiendrez une exception d'exécution

0
dmg