web-dev-qa-db-fra.com

Le constructeur générique dans une classe non générique est-il pris en charge?

N'est-il pas pris en charge, est-il pris en charge 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 constructeur, il n'y a pas de champ/propriété qui en dépend, je l'utilise (génériques) pour imposer la corrélation de types 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 une approche simple.

39
greenoldman

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

De temps en temps, je conviens que ce serait pratique - mais la syntaxe serait affreuse. Par exemple, supposons que vous ayez:

public class Foo<T> {}

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

Quel serait

new Foo<string>()

faire? Appelez le constructeur générique de la classe non générique ou le constructeur normal de la classe générique? Vous auriez à 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 appellerais-tu le constructeur? Espérons que nous pouvons tous convenir que:

new Foo<string><int>()

est assez hideux ...

Alors oui, sémantiquement, il serait parfois utile - mais la laideur qui en résulte contrebalance cela, malheureusement.

61
Jon Skeet

Les constructeurs génériques ne sont pas pris en charge, mais vous pouvez contourner ce problème en définissant simplement une méthode 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 ceci:

// 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);
20
Peter Alexander

Voici un exemple pratique expliquant comment vous souhaitez utiliser un paramètre de type constructeur supplémentaire et la solution de contournement.

Je vais vous présenter un simple wrapper 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 être bien. Mais tôt ou tard, vous souhaitez convertir un RefCounted<Control> en RefCounted<Button> tout en conservant le décompte des références d'objet, c'est-à-dire uniquement lorsque les deux instances sont disposées pour éliminer l'objet sous-jacent.

Le meilleur moyen est d’écrire (comme le font les utilisateurs de C++)

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

Mais C # ne le permet pas. La solution consiste donc à 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 possède 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 juste cacher un type au 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.

0
Earth Engine