1

Hello,

Pour ne pas changer, j'ai un petit problème de C# grin

J'ai défini quelque part une série de méthodes surchargées de ce style :
public static class Machin
{
    Int32 Methode (A value) { ... }
    Int32 Methode (B value) { ... }
    Int32 Methode (C value) { ... }
}

Je voudrais ensuite les utiliser au sein d'une classe générique, mettons "MaClasse<T>". Cette classe possède une méthode comme celle-ci :
class MaClasse<T>
{
    public void Test (T value)
    {
        Console.WriteLine (Machin.Methode (value));
    }
}

Le problème est que rien ne dit que "Machin.Methode" est valide pour le type T. Par ailleurs je ne peux pas toucher à A, B et C puisque ce sont des types de base comme String, DateTime ou Color.

L'idéal serait d'avoir une contrainte du style "where T in A, B, C", mais à ma connaissance on ne peut pas définir ce type de contrainte en C#. Pour l'instant j'ai donc été obligé de laisser la méthode "Test" abstraite et de définir "MaClasseString : MaClasse<String>", "MaClasseDateTime : MaClasse<DateTime>" et ainsi de suite, mais ça me fait créer tout un paquet de classes qui n'implémentent qu'une seule méthode qui s'écrit rigoureusement de la même façon à chaque fois :
abstract class MaClasse<T>
{
    public abstract void Test (T value);
}

class MaClasseString : MaClasse<String>
{
    public override void Test (String value)
    {
        Console.WriteLine (Machin.Methode (value));
    }
}

class MaClasseDateTime : MaClasse<DateTime>
{
    public override void Test (DateTime value)
    {
        Console.WriteLine (Machin.Methode (value));
    }
}

// ... etc.

Bref, beaucoup de code redondant pour rien. Existe-t-il une solution plus propre ?

Merci ^^
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

2

ça me parait chaud, comme tu dis, le compilateur ne peut pas vérifier que tu vas appeler Machin.Methode avec le bon type.

et donc effectivement il faudrait un genre de "where T in A, B, C" pour dire au compilo que ça peut pas être autre chose.

Et comme tu controles pas les types A,B,C tu peux pas dire qu'ils implémentent tous une interface commune ou qu'ils dérivent d'un même super type...

donc je dirais que la solution est "non y'en a pas grin"

(et en langue de Sun ça marche pas non plus ^^)

3

Certes, mais c'est pas non plus comme si le Java était une référence quand on parle de programmation générique ^^

Mais bon ça reste possible dans d'autres langages et ça fait plusieurs fois que j'en ai besoin en C# sans avoir de bonne solution, ça serait dommage qu'il n'y ait vraiment aucune autre façon de faire que d'écrire 15 fois les mêmes méthodes (en tout cas ça indiquerait clairement une lacune du langage).
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

4

en C#, il n'y a pas de notion de réflexion (aussi appelée introspection) comme en Java ?
Ca permet d'appeler dynamiquement une méthode en lui précisant le nom de la méthode, les types de paramètre et leur valeur.

Du coup, il suffirait d'appeler la méthode "Machin.Methode" avec, comme type de paramètre "typeof(value)" (je ne sais pas si ce genre d'instruction existe aussi) et comme valeur "value"...

Bon, c'est vrai, on ne peut pas vraiment appeler l'introspection du code propre... Mais si je devais définir une façon "propre" de le faire, ça prendrais plus de classes que ça grin
Rest... In... Peace

5

La réflexion permettrait d'y arriver oui, mais c'est mégalent ^^ (et puis ça implique de spécifier sous forme de chaines de caractère le nom des méthodes, donc le refactoring ne fonctionne plus, j'utilise vraiment ça quand il n'y a aucune autre solution)
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

6

Tiens, voilà un avantage des templates C++ par rapport aux génériques Java/C#, en C++, c'est possible d'écrire ça, les templates C++ sont une sorte de duck typing en temps de compilation.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

7

Ce genre de problèmes est assez ennuyeux oué (ne pas pouvoir spécialiser soi même un type générique) mais tu peux souvent t'en sortir de diverses façons.
Ton problème vient du fait que tu n'imposes pas de contrainte sur ton T. Dès lors, T peut être n'importe quoi, donc tu n'as aucune garantie que ce type implémente une méthode particulière.
Après il faut savoir (les raisons derrière tout ça, etc...), pas spécialement pour répondre à Zephyr, mais c'est toujours utile à savoir, que les tous les contrats en C# sont des contrats forts (les interfaces, qu'il faut explicitement implémenter... Il y aura le type dynamic pour remédier à ça en C# 4.0 mais ça ne s'appliquera pas vraiment de manière intéressante au cas présent je pense), et que les contraintes de types génériques sont quasi uniquement basées sur des contrats forts (il y a l'exception du new() et du class/struct, mais je crois que c'est tout).
Ce qui simplifierait tout serait la possibilité de spécialiser les interfaces soit même, ce qui revient en quelque sorte à la méthode de ./1, mais en mieux quand même, puisque la spécialisation serait transparente à l'utilisateur. (Pour ce qui est de la faisabilité au niveau du CLR j'en sais fichtrement rien après sad)

Pour ce qui est des solutions concrètes au problème, j'en vois basiquement deux.
1. La méthode que tu as choisie en ./1 ... A l'exception faite que si ton problème se situe au niveau de la méthode Machin.Methodeode (value)); } protected abstract Int32 Methode(T value); }
, c'est cette méthode (seule) que tu dois surcharger dans ta classe: abstract class MaClasse<T> 
{ 
    public void Test (T value) 
    { 
        Console.WriteLine (Meth

2. Utiliser une interface générique implémentant la méthode de tes désirs, ce qui est utilisé par exemple par le Dictionary<TKey,TValue> de la BCL actuelle.
interface IHasMethod<T>
{
    int Methode(T value);
}
Il te suffit ensuite de demander qu'on te fournisse cette interface quelque part dans ton constructeur, et d'accepter ou refuser, de gérer un comportement par défaut (par exemple, réflexion, ou accepter que le type "T" définisse lui même l'interface, etc ^^) dans ta classe.
Ce qui se simplifie évidemment grandement si (et seulement si) tu as le contrôle de ta classe Machin }
: public static class Machin : IHasMethod<A>, IHasMethod<B>, IHasMethod<C>
{ 
    Int32 Methode (A value) { ... } 
    Int32 Methode (B value) { ... } 
    Int32 Methode (C value) { ... } 


Après, les deux méthodes sont relativement équivalentes, mais je pense que l'une est préférable à l'autre selon les cas.
Typiquement, la première méthode s'appliquerait mieux dans le cas où tu veux améliorer le fonctionnement de la classe pour un ou deux types spécifiques, mais où elle fonctionne déjà pour pratiquement tout de base, tandis que la seconde méthode évite de devoir bidouiller la "machinerie interne" de ta classe (à priori j'aura tendance à dire que c'est ton cas en ./1 mais j'en sais peut-être pas assez), et ou tu peux ainsi limite la sceller (sealed c le bien... même si pour l'instant le JIT n'en tire aucune optimisation sad).

Tu peux après pousser un peu le vice pour la méthode 2:
>(); else if (StandardMethods.Default is IHasMethod<T>) methodOwner = StandardMethods.Default as IHasMethod<T>; else throw new InvalidOperationException(); } public void Test(T value) { Console.WriteLine(methodOwner.Method(value)); } }
1. Prévoir une implémentation par défaut pour tous les types que tu connais, autoriser le type à définir lui même la méthode, accepter une méthode spéciale via un paramètre  spécifique (éventuellement même avec un délégate...), et, pourquoi pas, offrir une alternative standard via la réflexion ou autre:sealed class StandardMethods : IHasMethod<A>, ...
{
    public static readonly StandardMethods Default = new StandardMethods();
    public int Method(A value) { ... }
}
class MyClass<T> 
{
    sealed class TypeHasMethod<T> : IHasMethod<T>
        where T : IHasMethod<T>
    {
        int Method(T value) { value.Method(value); }; // Ouais c'est complètement con comme implémentation mais ça fonctionne #trioui#
    }
    IHasMethod<T> methodOwner;

    public MyClass() : this(null) { }

    public MyClass(IHasMethod<T> methodOwner)
    {
        if (methodOwner != null)
            this.methodOwner = methodOwner;
        else if (typeof(IHasMethod<T>).IsAssignableFrom(typeof(T)))
            methodOwner = new TypeHasMethod<T
(J'avoue j'ai pas testé le code mais c'est l'idée générale quoi)
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

8

La solution #2 me plairait bien, mais j'ai du mal à voir où ça m'éviterait d'avoir à hériter N fois "MaClasse" (N étant le nombre de types à gérer) ?

[edit] ah ok j'ai capté avec ton code, mais il y a deux petits problèmes :

- comme indiqué au post ./1, ma classe "Machin" qui contient les méthodes est statique, donc ne peut pas implémenter d'interface
- l'idéal serait d'avoir des classes "MyClasse" les plus légères possibles (j'en ai *vraiment* beaucoup à gérer), donc le fait qu'elles doivent embarquer une référence vers leur générateur et surtout le fait de devoir le prendre en paramètre à la construction est assez gênant :/

mais merci pour les idées, ça me sera surement utile dans d'autres cas de figure ^^
%0
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

9

Bah si toutes tes méthodes sont définies dans une classe comme tu sembles l'indiquer, et si tu peux modifier le code de cette classe, ce n'est pas très compliqué vu que tu n'as qu'à rajouter le contrat d'interface dans la déclaration et tu gagnes automatiquement le comportement générique pour les types supportés. Sinon tu devras effectivement quand même réécrire du code quelque part, ce qui est... moins idéal on va dire, mais tu peux le fourrer dans la même classe. (celle offrant les implémentations par défaut par exemple, vu que c'est toi qui en écrit le code)
(Bon d'ailleurs je viens de voir que j'ai inversé la priorité "Type définit lui même la méthode" avec "Méthode standard disponible" je vais éditer ça tongue)
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

10

gni ? pour le coup j'ai rien compris grin

ajouter le contrat d'interface dans la déclaration de quoi ?
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

11

Hmm ouais ok j'avais pas vu le static caché, c'est mal... Il n'y était que sur la déclaration de la classe et pas des membres (ce qui est invalide cheeky)
Mais il y a d'autres solutions à ça. Après sachant que tu veux une classe la plus légère possible, je peux te proposer de t'en sortir en externalisant ta méthode Test. tongue
Si tu peux te permettre les fonctionnalités C# 3.0 ça se fait au coût d'une méthode d'extension (donc 3x rien) mais ça coûtera un peu plus au niveau de l'appel de la méthode tongue

En fait la liste des choses auxquelles je penses qui peuvent s'avérer utile: (Y'a pas vraiment d'ordre à chercher là dedans)
- Méthode d'extension
- Dictionnaire (genre type/objet MaClasse => interface/délégué/que sais-je)
- Délégués (C'est le seul type de contrat que tu pourras avoir avec des méthodes statiques hélas, mais c'est normal)
En mixant les 3 à ton gré tu auras peut-être une solution meilleure mais la ça dépendra complètement de ce que tu veux faire en vrai j'imagine.

./10> nan cherche pas j'avais posté avant ton edit cheeky
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

12

oui le code est invalide désolé (mais ça reste plus parlant que l'implémentation réelle je pense ^^)

ok pourle reste, je connais pas les méthodes d'extension, au pire si ça devient vraiment casse-gueule ou complexe tant pis je resterai sur l'héritage ; c'est redondant mais au moins c'est simple et efficace
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

13

Le problème de la classe statique est plutôt simple à résoudre pourtant, cf. singleton pattern. (Je ne sais pas si le code que je vais écrire est valide, je ne suis pas un expert du C# pour des raisons évidentes, mais c'est l'idée.)

class FooImpl<T> {
  public void Test(T value) { ... }
};

static class Foo {
  private static FooImpl<T> singleton = null;
  public static void Test(T value) {
    if (singleton == null) singleton = new FooImpl<T>();
    singleton.Test(value);
  }
};
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

14

Le singleton c'est ce que j'ai écrit dans mon code plus haut cheeky
(Faudrait juste ajouter un constructeur private mais bon...)
GoldenCrystal (./7) :
sealed class StandardMethods : IHasMethod<A>, ...
{
    public static readonly StandardMethods Default = new StandardMethods();
    public int Method(A value) { ... }
}

Y'a pas plus simple, efficace (et fiable) que ça pour ce cas précis tongue
En plus ton code est complètement faux, c'est même pas un singleton grin (C'est juste une instanciation tardive d'un membre privé d'une classe)
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

15

Ah bah mince, j'avais oublié un static. sad Maintenant c'est un singleton. tongue
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

16

C'est quand même toujours pas un vrai singleton (uniquement et au mieux par rapport à une seule classe, où c'est un membre privé, il n'a rien garantissant son unicité en dehors :/), et absolument pas thread-safe.

[HS]

n() { } public Singleton Default { get { return default; } } }
En C# tu ne trouveras pas de meilleur design que celui là à mon avis: sealed class Singleton
{
    private Singleton default = new Singleton();

    // Ou pas
    /* static Singleton() { } */

    Singleto
}
ousealed class Singleton
{
    public readonly Singleton Default = new Singleton();

    // Ou pas
    /* static Singleton() { } */

    Singleton() { }
Les deux déclarations sont équivalentes. A savoir juste que sans constructeur statique, il est possible que le Singleton soit instancié sans être utilisé par la suite (mais les performances seront meilleures d'un iota), et avec un constructeur statique tu ne prends pas ce risque, mais il est possible que les performances soient légèrement dégradées (comme il est possible que ça ne change rien mais ça dépend vraiment de ton code...)
Bien sur l'efficacité dépend vraiment du cas concret (as tu d'autres champs + méthodes / propriétés statiques ?) et il faut savoir ajuster le modèle en fonction des besoins. En cas de besoin, vous pouvez trouver le modèle de singleton "absolu" (tout comme le modèle moins absolu dans ce post) ici http://www.yoda.arachsys.com/csharp/ (qui est des meilleures références justement pour ce qui concerne les constructeurs statiques et l'implémentation du Singleton...) où je vous conseille de lire la section sur le Singleton ET celle sur beforefieldinit si le sujet vous intéresse.

[/HS]

} } class MyClass<T> { public void Test(T value) { Console.WriteLine(MethodManager.CallMethod(value)); } }
Sinon pour le problème de Zephyr, maintenant que j'ai l'esprit un peu plus clair (c'est le matin ici) voilà ce qui me semble convenir au problème:// 1. Définir type delegate qui correspondra au prototype de la fonction recherchée
delegate int MethodDelegate<T>(T value);

// 2. Définir un moyen d'associer ta fonction au type de manière efficace (on veut éviter la réflexion abusive car c'est lent)
// Ce n'est qu'un des nombreux moyen de faire mais il m'a tout à coup semblé plus intéressant au moment de l'écriture de ce post...
// Et je ne vais pas tous les énumérer afin de vous éviter un flood, bien que ce ne soit pas l'envie qui m'en manque :(
static class MethodManager
{
    static class MethodHolder<T>
    {
        public static MethodDelegate Method;
    }

    // Il faudra lier les références une à une, il y a 50 façons de faire également
    public static void Bind<T>(MethodDelegate<T> method)
    {
        MethodHolder<T>.Method = method;
    }

    // On apelle la gentille méthode
    public static int CallMethod<T>(T value)
    {
        // On ne vérifie (surtout) pas que la référence est valide car on s'en fout. (Si c'est nul c'est un bug et pis stout)
        // De toutes façons ça balancera une exception si elle ne l'est pas donc ça nous évite de le faire nous même.
        // Et surtout, comme ça on laisse la possibilité que le code soit passé inline :p
        MethodHolder<T>.Method(value);
Voilà, je ne sais pas ce que tu penses de celle là, mais c'est une équivalence quasi parfaite aux interfaces. Normalement le coût d'un appel de délégué est à peu de choses près identique au coût d'un appel d'interface donc il ne devrait pas y avoir trop de différences.
Evidemment il faudra quand même initialiser les références quelque part comme pour les interfaces, et ici le code ne fonctionne que si tu as l'équivalence UN type <=> UNE méthode, car il utilise des références globales pour t'éviter la moindre référence locale. Je n'énumère pas non plus les variations possibles, enfin voilà tongue
(Bon faudrait quand même vérifier que tout ça compile sans erreur mais normalement pas de souci)
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

17

Ah pas mal, je ne connaissais pas cette possibilité d'initialiser un membre statique générique plusieurs fois avec des types différents top

Merci smile
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

18

Oui en fait, contrairement au java (dont les types génériques sux complètement), pour chaque paramètre de type possible tu as, aux détails d'implémentation près, une classe totalement différente. Evidemment seules les combinaisons de types que tu utilises en pratique sont instanciées, mais ça veut dire que le <T> fait partie du nom de la classe. Et du coup avec ce que j'ai posté il faut faire attention à ne pas appeler le code avec tous les types du framework sinon ça va créer une classe pour chaque type qui existe, et comme on ne peut pas décharger les classes ça va juste saturer la mémoire...
lass { ... } sealed class MyClass<T> : MyClass { ... }En fait tu peux même faire un truc sympa dans ce genre:abstract class MyCC'est assez pratique avec le C# actuel, car ça te permet d'avoir un type de base connu quand tu ne connais pas les "T" à l'avance. Avec le C# 4.0 on pourra utiliser la variance des interfaces (dans certains cas) pour ça donc ça sera bien mieux, mais bon, on y est pas encore hélas sad

Sinon, j'ai fait un exemple plus complet qui fonctionne (en fait je voulais surtout voir si ça permettait de rendre possible l'utilisation des méthodes d'extension avec les types génériques, et ça peut tongue) donc je vais mettre le fichier ici avant de poster ça sur mon blog quand j'aurai pas la flemme de le faire: tromb Fichier joint : Program.cs
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

19

j'aurais trouvé plus naturel d'avoir un "where" qui permette de spécifier une liste de types, mais faute de mieux c'est une solution assez élégante, merci pour l'astuce smile

[hs]tiens j'ai oublié de gérer le bom utf8 ^^[/hs]
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)

20

De toute façon, le BOM n'a rien à faire dans un fichier UTF-8. roll
Note that some recipients of UTF-8 encoded data do not expect a BOM. Where UTF-8 is used transparently in 8-bit environments, the use of a BOM will interfere with any protocol or file format that expects specific ASCII characters at the beginning, such as the use of "#!" of at the beginning of Unix shell scripts

Cf. aussi http://www.unicode.org/versions/Unicode5.0.0/ch02.pdf:
Use of a BOM is neither required nor recommended for UTF-8
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

21

22

En l'occurence Visual Studio met ça par défaut dans tout ses fichiers UTF-8 pour les discerner de simples fichiers ASCII, ce qui, je pense [pas] que tu comprendras, simplifie bien des opérations, et c'est parfaitement géré par tout les outils de la chaîne de compilation ainsi que par la BCL .NET en elle-même (mais j'avoue, je me suis arraché des cheveux pendant des heures avec un fichier .fx qui voulait pas compiler en dehors de Visual Studio à cause de ça) donc je vois pas en quoi ça te pose problème à toi Kevin...
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

23

Parce que ça crée des erreurs ("stray character") avec pratiquement tous les outils *nix.
L'avantage de ne pas mettre le BOM est justement que les outils n'ont pas besoin de savoir que c'est de l'UTF-8, c'est parfaitement compatible ASCII comme n'importe quel autre charset 8 bits courant.
Quant aux éditeurs, ben sous *nix ils présupposent que tout est en UTF-8 par défaut tout simplement.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

24

Sauf qu'on s'en fout d'Unix, et surtout dans ce topic. Fin du HS.
avatar
All right. Keep doing whatever it is you think you're doing.
------------------------------------------
Besoin d'aide sur le site ? Essayez par ici :)