web-dev-qa-db-fra.com

Interface avec paramètre générique vs Interface avec méthodes génériques

Disons que j'ai une telle interface et une implémentation concrète

public interface IMyInterface<T>
{
    T My();
}

public class MyConcrete : IMyInterface<string>
{
    public string My()
    {
        return string.Empty;
    }
}

Je crée donc une implémentation MyConcrete pour strings, je peux avoir une implémentation plus concrète pour int. Et ça va. Mais disons que je veux faire la même chose, mais avec des méthodes génériques, j'ai donc

public interface IMyInterface2
{
    T My<T>();
}

public class MyConcrete2 : IMyInterface2
{
    public string My<string>()
    {
        throw new NotImplementedException();
    }
}

J'ai donc le même IMyInterface2, Mais qui définit le comportement générique au moyen de T My<T>(). Dans ma classe concrète, je veux implémenter le comportement My, mais pour le type de données concret - string. Mais C # ne me permet pas de faire ça.

Ma question est pourquoi je ne peux pas faire ça? En d'autres termes, si je peux créer une implémentation concrète de MyInterface<T> En tant que MyClass : MyInterface<string> Et arrêter le caractère générique à ce stade, pourquoi je ne peux pas faire cela avec la méthode générique - T My<T>()?

34
Jevgenij Nekrasov

L'implémentation de votre méthode générique doit également être générique, elle doit donc être:

public class MyConcrete2 : IMyInterface2
{
    public T My<T>()
    {
        throw new NotImplementedException();
    }
}

Pourquoi vous ne pouvez pas faire My<string>() ici? Parce que le contrat d'interface a besoin d'une méthode, elle peut être appelée avec n'importe quel paramètre de type T et vous devez respecter ce contrat.

Pourquoi vous ne pouvez pas arrêter le caractère générique à ce stade ? Parce que cela provoquerait des situations comme les suivantes:

Déclarations de classe:

public interface IMyInterface2
{
    T My<T>(t value);
}

public class MyClass21 : IMyInterface2
{
    public string My<string>(string value) { return value; }
}

public class MyClass22 : IMyInterface2
{
    public int My<int>(int value) { return value; }
}

Utilisation:

var item1 = new MyClass21();
var item2 = new MyClass22();

// they both implement IMyInterface2, so we can put them into list
var list = new List<IMyInterface2>();
list.Add(item1);
list.Add(item2);

// iterate the list and call My method
foreach(IMyInterface2 item in list)
{
    // item is IMyInterface2, so we have My<T>() method. Choose T to be int and call with value 2:
    item.My<int>(2);

    // how would it work with item1, which has My<string> implemented?
}
35
MarcinJuraszek

Parce que votre interface déclare une méthode générique T My<T>(), mais votre implémentation n'implémente pas une fonction avec cette signature spécifique.

Pour obtenir ce que vous voulez, vous devez plutôt fournir le paramètre générique T à l'interface, dans votre premier exemple:

public interface IMyInterface2<T>
{
        T My();
}

public class MyConcrete2 : IMyInterface2<string>
{
    public string My()
    {
        throw new NotImplementedException();
    }
}
3

lorsque vous écrivez la méthode générique, la définition sert à conserver l'espace réservé. Le type réel apparaît dans l'image lorsque vous appelez la méthode. vous devriez donc écrire

public T My<T>()
{
    throw new NotImplementedException();
}

et lorsque vous appelez la méthode, vous pouvez y utiliser la chaîne.

3
Rakesh

Votre solution ne fonctionne pas pour deux raisons.

Premièrement, une interface est un contrat. Lorsque vous implémentez IMyInterface2 vous garantissez que vous implémenterez une fonction nommée My qui prend un paramètre de type générique et renvoie ce type. MyConcrete2 ne fait pas cela.

Deuxièmement, les génériques C # ne permettent aucun type de spécialisation de paramètre de type. (Je souhaite que C # le prenne en charge.) C'est une chose courante dans les modèles C++ où votre exemple compilerait, mais toutes les utilisations de MyConcrete2 échouerait à la compilation s'ils n'appelaient pas My avec un string.

1
IngisKahn