56 votes

Le constructeur générique d'une classe non générique est-il supporté ?

Est-ce qu'il n'est pas supporté, est-ce qu'il est supporté mais je dois faire quelques astuces ?

Exemple :

class Foo
{
  public Foo<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2)
  {
     ...
  }
}

les génériques ne sont utilisés que dans le constructeur, il n'y a pas de champ/propriété dépendant d'eux, je les utilise (génériques) pour renforcer la corrélation de type pour f1 et f2.

Remarque : J'ai trouvé la solution de contournement -- méthode statique Create, mais de toute façon je suis curieux de savoir pourquoi j'ai un problème avec l'approche directe.

87voto

Jon Skeet Points 692016

Non, les constructeurs génériques ne sont pas pris en charge dans les classes génériques ou non génériques. De même, les événements, propriétés et finaliseurs génériques ne sont pas pris en charge.

Juste à l'occasion, je suis d'accord pour dire que ce serait pratique - mais la syntaxe serait plutôt affreuse. Par exemple, supposons que vous ayez :

public class Foo<T> {}

public class Foo
{
    public Foo<T>() {}
}

Qu'est-ce que

new Foo<string>()

faire ? Appeler le constructeur générique de la classe non générique, ou le constructeur normal de la classe générique ? Il faudrait les différencier d'une manière ou d'une autre, et ce serait compliqué :(

De même, considérons un constructeur générique dans une classe générique :

public class Foo<TClass>
{
    public Foo<TConstructor>() {}
}

Comment appelez-vous le constructeur ? J'espère que nous sommes tous d'accord sur ce point :

new Foo<string><int>()

est assez hideux...

Donc oui, sémantiquement, ce serait occasionnellement utile - mais la laideur qui en résulterait contrebalancerait cela, malheureusement.

34voto

Peter Alexander Points 31990

Les constructeurs génériques ne sont pas pris en charge, mais vous pouvez contourner ce problème en définissant simplement un générique, static qui renvoie une nouvelle Foo :

class Foo
{
  public static Foo CreateFromFuncs<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2)
  {
     ...
  }
}

qui est utilisé comme suit :

// create generic dependencies
var func1 = new Func<byte, string>(...);
var func2 = new Func<string, byte>(...);

// create nongeneric Foo from dependencies
Foo myFoo = Foo.CreateFromFuncs<byte, string>(func1, func2);

1voto

Earth Engine Points 1795

Voici un exemple concret de la manière dont vous souhaitez disposer d'un paramètre supplémentaire pour le type de constructeur, ainsi que la solution de contournement.

Je vais introduire une simple RefCounted pour IDisposable :

public class RefCounted<T> where T : IDisposable
{
    public RefCounted(T value)
    {
        innerValue = value;
        refCount = 1;
    }

    public void AddRef()
    {
        Interlocked.Increment(ref refCount);
    }

    public void Dispose()
    {
        if(InterlockedDecrement(ref refCount)<=0)
            innerValue.Dispose();
    }

    private int refCount;
    private readonly innerValue;
}

Cela semble bien se passer. Mais tôt ou tard, vous souhaitez lancer un RefCounted<Control> a RefCounted<Button> tout en conservant le comptage des références des deux objets, c'est-à-dire qu'il suffit de disposer des deux instances pour disposer de l'objet sous-jacent.

Le meilleur moyen est d'écrire (comme les gens du C++ peuvent le faire)

public RefCounted(RefCounted<U> other)
{
    ...whatever...
}

Mais le C# ne le permet pas. La solution est donc d'utiliser une certaine indirection.

private readonly Func<T> valueProvider;
private readonly Action disposer;

private RefCounted(Func<T> value_provider, Action disposer)
{
    this.valueProvider = value_provider;
    this.disposer = disposer;
}

public RefCounted(T value) : this(() => value, value.Dispose)
{
}

public RefCounted<U> Cast<U>() where U : T 
{
    AddRef();
    return new RefCounted<U>(() => (U)(valueProvider()),this.Dispose);
}

public void Dispose(){
    if(InterlockedDecrement(ref refCount)<=0)
        disposer();
}

Si votre classe a des champs de type générique, vous n'avez pas d'autre choix que de mettre tous ces types dans la classe. Cependant, si vous voulez simplement cacher certains types du constructeur, vous devrez utiliser l'astuce ci-dessus - avoir un constructeur caché pour tout mettre ensemble, et définir une fonction générique normale pour appeler ce constructeur.

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