C ++ et C # manquent tous deux de moyens simples pour créer un nouveau type qui est sémantiquement identique à un type existant. Je trouve ces 'typedefs' totalement essentiels pour une programmation sécurisée et c'est vraiment dommage que c # ne les ait pas intégrés. La différence entre void f(string connectionID, string username)
to void f(ConID connectionID, UserName username)
est évidente ...
(Vous pouvez réaliser quelque chose de similaire en C ++ avec boost dans BOOST_STRONG_TYPEDEF)
Il peut être tentant d'utiliser l'héritage mais cela a quelques limitations majeures:
- cela ne fonctionnera pas pour les types primitifs
- le type dérivé peut toujours être casté dans le type d'origine, c'est-à-dire que nous pouvons l'envoyer à une fonction recevant notre type d'origine, ce qui va à l'encontre de l'objectif
- nous ne pouvons pas dériver de classes scellées (et donc de nombreuses classes .NET sont scellées)
La seule façon de réaliser une chose similaire en C # est de composer notre type dans une nouvelle classe:
Class SomeType {
public void Method() { .. }
}
sealed Class SomeTypeTypeDef {
public SomeTypeTypeDef(SomeType composed) { this.Composed = composed; }
private SomeType Composed { get; }
public override string ToString() => Composed.ToString();
public override int GetHashCode() => HashCode.Combine(Composed);
public override bool Equals(object obj) => obj is TDerived o && Composed.Equals(o.Composed);
public bool Equals(SomeTypeTypeDefo) => object.Equals(this, o);
// proxy the methods we want
public void Method() => Composed.Method();
}
Bien que cela fonctionne, il est très détaillé pour un typedef. De plus, nous avons un problème avec la sérialisation (ie vers Json) car nous voulons sérialiser la classe via sa propriété Composed.
Vous trouverez ci-dessous une classe d'assistance qui utilise le «modèle de modèle curieusement récurrent» pour simplifier les choses:
namespace Typedef {
[JsonConverter(typeof(JsonCompositionConverter))]
public abstract class Composer<TDerived, T> : IEquatable<TDerived> where TDerived : Composer<TDerived, T> {
protected Composer(T composed) { this.Composed = composed; }
protected Composer(TDerived d) { this.Composed = d.Composed; }
protected T Composed { get; }
public override string ToString() => Composed.ToString();
public override int GetHashCode() => HashCode.Combine(Composed);
public override bool Equals(object obj) => obj is Composer<TDerived, T> o && Composed.Equals(o.Composed);
public bool Equals(TDerived o) => object.Equals(this, o);
}
class JsonCompositionConverter : JsonConverter {
static FieldInfo GetCompositorField(Type t) {
var fields = t.BaseType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (fields.Length!=1) throw new JsonSerializationException();
return fields[0];
}
public override bool CanConvert(Type t) {
var fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);
return fields.Length == 1;
}
// assumes Compositor<T> has either a constructor accepting T or an empty constructor
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
while (reader.TokenType == JsonToken.Comment && reader.Read()) { };
if (reader.TokenType == JsonToken.Null) return null;
var compositorField = GetCompositorField(objectType);
var compositorType = compositorField.FieldType;
var compositorValue = serializer.Deserialize(reader, compositorType);
var ctorT = objectType.GetConstructor(new Type[] { compositorType });
if (!(ctorT is null)) return Activator.CreateInstance(objectType, compositorValue);
var ctorEmpty = objectType.GetConstructor(new Type[] { });
if (ctorEmpty is null) throw new JsonSerializationException();
var res = Activator.CreateInstance(objectType);
compositorField.SetValue(res, compositorValue);
return res;
}
public override void WriteJson(JsonWriter writer, object o, JsonSerializer serializer) {
var compositorField = GetCompositorField(o.GetType());
var value = compositorField.GetValue(o);
serializer.Serialize(writer, value);
}
}
}
Avec Composer, la classe ci-dessus devient simplement:
sealed Class SomeTypeTypeDef : Composer<SomeTypeTypeDef, SomeType> {
public SomeTypeTypeDef(SomeType composed) : base(composed) {}
// proxy the methods we want
public void Method() => Composed.Method();
}
Et en plus la SomeTypeTypeDef
volonté sérialisera à Json de la même manière qui SomeType
fait.
J'espère que cela t'aides !