web-dev-qa-db-fra.com

Équivalent de typedef en C #

Existe-t-il un équivalent typedef en C # ou un comportement similaire? J'ai fait quelques recherches sur Google, mais partout où je regarde semble être négatif. Actuellement, j'ai une situation semblable à la suivante:

class GenericClass<T> 
{
    public event EventHandler<EventData> MyEvent;
    public class EventData : EventArgs { /* snip */ }
    // ... snip
}

Désormais, il n’est pas difficile pour un spécialiste de la fusée de comprendre que cela peut rapidement entraîner beaucoup de dactylographie (excuses pour le jeu de mots horrible) lorsqu’on essaie de mettre en place un gestionnaire pour cet événement. Cela finirait par être quelque chose comme ça:

GenericClass<int> gcInt = new GenericClass<int>;
gcInt.MyEvent += new EventHandler<GenericClass<int>.EventData>(gcInt_MyEvent);
// ...

private void gcInt_MyEvent(object sender, GenericClass<int>.EventData e)
{
    throw new NotImplementedException();
}

Sauf que, dans mon cas, j'utilisais déjà un type complexe, pas seulement un int. Ce serait bien s'il était possible de simplifier un peu ...

Edit: ie. en tapant peut-être le gestionnaire d'événements au lieu d'avoir à le redéfinir pour obtenir un comportement similaire.

295
Matthew Scharley

Non, il n'y a pas de véritable équivalent de typedef. Vous pouvez utiliser les directives "using" dans un fichier, par exemple.

using CustomerList = System.Collections.Generic.List<Customer>;

mais cela n'aura qu'un impact sur ce fichier source. D'après mes expériences en C et C++, typedef est généralement utilisé dans les fichiers .h, qui sont largement inclus, de sorte qu'un seul typedef puisse être utilisé sur un projet entier. Cette capacité n'existe pas en C #, car il n'y a pas de fonctionnalité #include en C # qui vous permettrait d'inclure les directives using d'un fichier à un autre.

Heureusement, l'exemple que vous donnez a une conversion fixe - groupe de méthodes implicite. Vous pouvez modifier votre ligne d'abonnement d'événement pour simplement:

gcInt.MyEvent += gcInt_MyEvent;

:)

317
Jon Skeet

Jon a vraiment donné une belle solution, je ne savais pas que vous pouviez le faire!

Parfois, j'ai eu recours à l'héritage de la classe et à la création de ses constructeurs. Par exemple.

public class FooList : List<Foo> { ... }

Ce n’est pas la meilleure solution (à moins que votre Assemblée ne soit utilisée par d’autres personnes), mais cela fonctionne.

36

Si vous savez ce que vous faites, vous pouvez définir une classe avec des opérateurs implicites pour effectuer une conversion entre la classe alias et la classe réelle.

class TypedefString // Example with a string "typedef"
{
    private string Value = "";
    public static implicit operator string(TypedefString ts)
    {
        return ((ts == null) ? null : ts.Value);
    }
    public static implicit operator TypedefString(string val)
    {
        return new TypedefString { Value = val };
    }
}

En fait, je n’approuve pas cela et n’ai jamais utilisé quelque chose comme cela, mais cela pourrait probablement fonctionner dans certaines circonstances.

18
palswim

C # supporte une covariance héritée pour les délégués d'événement, donc une méthode comme celle-ci:

void LowestCommonHander( object sender, EventArgs e ) { ... } 

Peut être utilisé pour s'abonner à votre événement, aucune distribution explicite requise

gcInt.MyEvent += LowestCommonHander;

Vous pouvez même utiliser la syntaxe lambda et l'intellisense sera fait pour vous:

gcInt.MyEvent += (sender, e) =>
{
    e. //you'll get correct intellisense here
};
6
Keith

Je pense qu'il n'y a pas de typedef. Vous pouvez uniquement définir un type de délégué spécifique au lieu du type générique dans GenericClass, c.-à-d.

public delegate GenericHandler EventHandler<EventData>

Cela le rendrait plus court. Mais qu'en est-il de la suggestion suivante:

Utilisez Visual Studio. De cette façon, quand vous avez tapé

gcInt.MyEvent += 

il fournit déjà la signature complète du gestionnaire d'événements d'Intellisense. Appuyez sur TAB et c'est là. Acceptez le nom du gestionnaire généré ou modifiez-le, puis appuyez à nouveau sur la touche de tabulation pour générer automatiquement le talon du gestionnaire.

5
OregonGhost

Vous pouvez utiliser une bibliothèque open source et un package NuGet appelé LikeType que j'ai créé et qui vous donneront le comportement GenericClass<int> que vous recherchez.

Le code ressemblerait à ceci:

public class SomeInt : LikeType<int>
{
    public SomeInt(int value) : base(value) { }
}

[TestClass]
public class HashSetExample
{
    [TestMethod]
    public void Contains_WhenInstanceAdded_ReturnsTrueWhenTestedWithDifferentInstanceHavingSameValue()
    {
        var myInt = new SomeInt(42);
        var myIntCopy = new SomeInt(42);
        var otherInt = new SomeInt(4111);

        Assert.IsTrue(myInt == myIntCopy);
        Assert.IsFalse(myInt.Equals(otherInt));

        var mySet = new HashSet<SomeInt>();
        mySet.Add(myInt);

        Assert.IsTrue(mySet.Contains(myIntCopy));
    }
}
3
Matt Klein

Voici le code correspondant, amusez-vous !, j'ai relevé cela du type dotNetReference dans l'instruction "using" de la ligne 106 de l'espace de noms http://referencesource.Microsoft.com /#mscorlib/Microsoft/win32/win32native.cs

using System;
using System.Collections.Generic;
namespace UsingStatement
{
    using Typedeffed = System.Int32;
    using TypeDeffed2 = List<string>;
    class Program
    {
        static void Main(string[] args)
        {
        Typedeffed numericVal = 5;
        Console.WriteLine(numericVal++);

        TypeDeffed2 things = new TypeDeffed2 { "whatever"};
        }
    }
}
2
shakram02

Il manque à C++ et à C # un moyen simple de créer un type nouvea identique sur le plan sémantique à un type existant. Je trouve que de tels 'types de caractères' sont absolument essentiels pour la programmation sans danger pour le type et c'est vraiment dommage que c # ne les ait pas intégrés. La différence entre void f(string connectionID, string username) à void f(ConID connectionID, UserName username) est évidente ...

(Vous pouvez obtenir quelque chose de similaire en C++ avec boost dans BOOST_STRONG_TYPEDEF)

Il peut être tentant d'utiliser l'héritage, mais cela comporte certaines limitations majeures:

  • cela ne fonctionnera pas pour les types primitifs
  • le type dérivé peut toujours être converti dans le type d'origine, c'est-à-dire que nous pouvons l'envoyer à une fonction recevant notre type d'origine, cela annule le but
  • nous ne pouvons pas dériver de classes scellées (et c’est-à-dire que beaucoup de classes .NET sont scellées)

Le seul moyen de parvenir à un résultat similaire en C # consiste à 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 juste un typedef. De plus, nous avons un problème avec la sérialisation (c'est-à-dire vers Json) car nous voulons sérialiser la classe par le biais de sa propriété Composed.

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 sera sérialisée sur Json de la même manière que SomeType.

Avoir besoin de méthodes proxy peut être un problème mais est aussi une bénédiction déguisée, étant un nouveau type, nous ne voulons souvent que sélectionner les méthodes sélectionnées et en ajouter de nouvelles à notre "typedef"

J'espère que cela t'aides !

2
kofifus

La meilleure alternative à typedef que j'ai trouvée dans C # est using. Par exemple, je peux contrôler la précision de float via les drapeaux du compilateur avec ce code:

#if REAL_T_IS_DOUBLE
using real_t = System.Double;
#else
using real_t = System.Single;
#endif

Malheureusement, il faut que vous placiez ceci en haut de chaque fichier où vous utilisez real_t. Il n’existe actuellement aucun moyen de déclarer un type d’espace de noms global en C #.

0
Aaron Franke