2010-10-21 4 views
2

J'ai un espace de noms de structures qui représentent diverses unités de mesure (Mètres, Pieds, Pouces, etc.) ... 12 au total, générées avec l'aimable autorisation des modèles T4 :).Comment tapez-vous directement une structure encadrée en C#?

Chaque struct porte les opérateurs de coulée implicites pour soutenir la coulée de la valeur à une autre valeur de type de mesure, de sorte que le sytax suivant est légal:

var oneThousandMeters = new Meters(1000);  
Kilometers aKilo = oneThousandMeters ;  // implicit cast OK. Value = 1 Km 

Pour ajouter à la joie, il y a un fourre-tout classe appelée Distance qui peut contenir une unité de mesure, et peut également être implicitement et à partir et la valeur de mesure ...

var magnum = new Distance(12, DistanceUnits.Inches); 
Feet wifesDelight = magnum;    // implicit cast OK. Value = 1 foot. 

Suite à la norme .NET Framework, la mise en forme de chaîne et l'analyse syntaxique est Handl ed par external un FormatProvider, qui implémente ICustomFormatter. Malheureusement, cela signifie que la valeur est encadrée quand elle est passée à la méthode Format, et la méthode de format doit tester l'objet par rapport à chaque type de mesure connu avant qu'il puisse agir sur lui. En interne, la méthode Format jette simplement la mesure à une valeur de distance de toute façon, est ici la question si ....

Question:

public string Format(string format, object arg, IFormatProvider formatProvider) 
{ 
    Distance distance;   

    // The following line is desired, but fails if arg != typeof(Distance) 
    distance = (Distance)arg;  

    // But the following tedious code works: 
    if(arg is Distance) 
     distance = (Distance)arg; 
    else if(arg is Meters) 
     distance = (Distance)(Meters)arg;  // OK. compile uses implicit cast. 
    else if(arg is Feet) 
     distance = (Distance)(Feet)arg;  // OK. compile uses implicit cast. 
    else if(arg is Inches) 
     distance = (Distance)(Inches)arg;  // OK. compile uses implicit cast. 
    else 
     ... // tear you hair out for all 12 measurement types 
} 

Existe-t-il des solutions pour cela, ou est-ce juste un des ces inconvénients insolubles des types de valeur? PS: J'ai vérifié this post, et bien que la question soit similaire, ce n'est pas ce que je cherche.

Répondre

3

Eh bien, il est une question de séparer la conversion unboxing de la conversion définie par l'utilisateur. Vous voulez que les deux se produisent - et vous devez spécifier le type à unbox, ainsi que de faire savoir au compilateur quand vous voulez une conversion définie par l'utilisateur. La conversion définie par l'utilisateur doit être sélectionnée à . Compilez à moins que vous n'utilisiez le typage dynamique. Le compilateur doit donc savoir de quel type il essaie de convertir.

Une option consiste à avoir une interface IDistance que toutes les structures implémentent. Ensuite, vous pouvez simplement utiliser:

IDistance distanceArg = arg as IDistance; 
if (distanceArg != null) 
{ 
    Distance distance = distanceArg.ToDistance(); 
} 

Comme vous avez une valeur déjà en boîte, en utilisant une interface ne provoque pas la boxe supplémentaire ou quelque chose comme ça. Chaque mise en œuvre ToDistance peut probablement utiliser la conversion implicite:

public Distance ToDistance() 
{ 
    return this; 
} 

... ou vous pouvez faire l'utiliser la conversion ToDistance.

+0

Jon à la rescousse encore ... merci beaucoup pour fournir une autre réponse brillante ici sur SO! Très appréciée. – Mark

+0

comme une barre latérale - considérez-vous mal de générer ces différents types de valeur en premier lieu, étant donné qu'il est possible d'encapsuler toutes leurs fonctionnalités dans la structure Distance? – Mark

+0

@Mark: Pas clair ... Je suis confronté à un dilemme similaire dans NodaTime en ce moment.Il y a beaucoup de façons de représenter les unités, et j'en ai assez peur pour juger. Je vous recommande de regarder ce que fait F #, pour une inspiration possible. –

1

Oui, c'est juste une de ces choses avec lesquelles vous devez vivre.

vous rencontrez la même chose si vous fourrer un entier dans un objet:

int a = 0; 
object b = a; 
int c = (int)b; // this works 
short d = (short)b; // this fails 
short e = (short)(int)b; // this works 
Questions connexes