web-dev-qa-db-fra.com

Existe-t-il un constructeur générique avec contrainte de paramètre en C #?

En C #, vous pouvez imposer une contrainte à une méthode générique telle que:

public class A {

    public static void Method<T> (T a) where T : new() {
        //...do something...
    }

}

Où vous spécifiez que T devrait avoir un constructeur qui ne nécessite aucun paramètre. Je me demande s’il existe un moyen d’ajouter une contrainte comme "il existe un constructeur avec un float[,] paramètre? "

Le code suivant ne compile pas:

public class A {

    public static void Method<T> (T a) where T : new(float[,] u) {
        //...do something...
    }

}

Une solution de contournement est également utile?

155
Willem Van Onsem

Comme vous l'avez découvert, vous ne pouvez pas faire cela.

En guise de solution de contournement, je fournis normalement un délégué qui peut créer des objets de type T:

public class A {

    public static void Method<T> (T a, Func<float[,], T> creator) {
        //...do something...
    }

}
138
Tim Robinson

Il n'y a pas une telle construction. Vous pouvez uniquement spécifier une contrainte de constructeur vide.

Je travaille autour de ce problème avec les méthodes lambda.

public static void Method<T>(Func<int,T> del) {
  var t = del(42);
}

Cas d'utilisation

Method(x => new Foo(x));
41
JaredPar

En utilisant la réflexion pour créer un objet générique, le type a toujours besoin du constructeur déclaré correct ou une exception sera levée. Vous pouvez passer n'importe quel argument tant qu'il correspond à l'un des constructeurs.

Utilisé de cette manière, vous ne pouvez pas imposer de contrainte au constructeur dans le modèle. Si le constructeur est manquant, une exception doit être gérée au moment de l'exécution plutôt que d'obtenir une erreur lors de la compilation.

// public static object CreateInstance(Type type, params object[] args);

// Example 1
T t = (T)Activator.CreateInstance(typeof(T));
// Example 2
T t = (T)Activator.CreateInstance(typeof(T), arg0, arg1, arg2, ...);
// Example 3
T t = (T)Activator.CreateInstance(typeof(T), (string)arg0, (int)arg1, (bool)arg2);
40
xpress

Voici une solution de contournement pour cela que je trouve personnellement très efficace. Si vous pensez à ce qu'est une contrainte de constructeur paramétrée générique, il s'agit en réalité d'un mappage entre types et constructeurs avec une signature particulière. Vous pouvez créer votre propre correspondance en utilisant un dictionnaire. Placez-les dans une classe "factory" statique et vous pourrez créer des objets de types variés sans avoir à vous soucier de la construction d'un constructeur lambda à chaque fois:

public static class BaseTypeFactory
{
   private delegate BaseType BaseTypeConstructor(int pParam1, int pParam2);

   private static readonly Dictionary<Type, BaseTypeConstructor>
   mTypeConstructors = new Dictionary<Type, BaseTypeConstructor>
   {
      { typeof(Object1), (pParam1, pParam2) => new Object1(pParam1, pParam2) },
      { typeof(Object2), (pParam1, pParam2) => new Object2(pParam1, pParam2) },
      { typeof(Object3), (pParam1, pParam2) => new Object3(pParam1, pParam2) }
   };

puis dans votre méthode générique, par exemple:

   public static T BuildBaseType<T>(...)
      where T : BaseType
   {
      ...
      T myObject = (T)mTypeConstructors[typeof(T)](value1, value2);
      ...
      return myObject;
   }
16
Dave Cousineau

Non. Pour le moment, la seule contrainte de constructeur que vous pouvez spécifier concerne un constructeur sans argument.

6
Sean Reilly

Je pense que c'est la solution la plus propre qui soit, en quelque sorte, une contrainte sur la manière dont un objet est construit. Le temps de compilation n’est pas entièrement vérifié. Lorsque vous avez convenu de faire en sorte que le constructeur des classes ait la même signature que l'interface IConstructor, c'est un peu comme si vous aviez une contrainte sur le constructeur. La méthode Constructor est masquée lorsque vous travaillez normalement avec l'objet, en raison de la mise en œuvre de l'interface explicite.

using System.Runtime.Serialization;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            var employeeWorker = new GenericWorker<Employee>();
            employeeWorker.DoWork();
        }
    }

    public class GenericWorker<T> where T:IConstructor
    {
        public void DoWork()
        {
            T employee = (T)FormatterServices.GetUninitializedObject(typeof(T));
            employee.Constructor("John Doe", 105);
        }
    }

    public interface IConstructor
    {
        void Constructor(string name, int age);
    }

    public class Employee : IConstructor
    {
        public string Name { get; private set; }
        public int Age { get; private set; }

        public Employee(string name, int age)
        {
            ((IConstructor)this).Constructor(name, age);
        }

        void IConstructor.Constructor(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }
}
3
Mike de Klerk

Pourquoi ne pas créer votre classe générique avec des contraintes? J'ai choisi ici struct et class pour avoir des types value et reference.

De cette façon, votre constructeur a une contrainte sur les valeurs.

class MyGenericClass<T, X> where T :struct where X: class 
{
    private T genericMemberVariableT;
    private X genericMemberVariableX;
    public MyGenericClass(T valueT, X valueX)
    {
        genericMemberVariableT = valueT;
        genericMemberVariableX = valueX;
    }

    public T genericMethod(T genericParameter)
    {
        Console.WriteLine("Parameter type: {0}, value: {1}", typeof(T).ToString(), genericParameter);
        Console.WriteLine("Return type: {0}, value: {1}", typeof(T).ToString(), genericMemberVariableT);
        Console.WriteLine("Return type: {0}, value: {1}", typeof(X).ToString(), genericMemberVariableX);
        return genericMemberVariableT;
    }

    public T genericProperty { get; set; }
}

La mise en oeuvre:

        MyGenericClass<int, string> intGenericClass = new MyGenericClass<int, string>(10, "Hello world");
        int val = intGenericClass.genericMethod(200);
1