Entspricht typedef in C #

326

Gibt es ein typedef-Äquivalent in C # oder eine Möglichkeit, ein ähnliches Verhalten zu erzielen? Ich habe ein bisschen gegoogelt, aber überall, wo ich hinschaue, scheint es negativ zu sein. Derzeit habe ich eine ähnliche Situation wie die folgende:

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

Jetzt braucht ein Raketenwissenschaftler nicht mehr, um herauszufinden, dass dies sehr schnell zu viel Tippen führen kann (Entschuldigung für das schreckliche Wortspiel), wenn er versucht, einen Handler für dieses Ereignis zu implementieren. Es würde ungefähr so ​​aussehen:

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();
}

Außer in meinem Fall habe ich bereits einen komplexen Typ verwendet, nicht nur einen int. Es wäre schön, wenn es möglich wäre, dies ein wenig zu vereinfachen ...

Bearbeiten: dh. Vielleicht müssen Sie den EventHandler eingeben, anstatt ihn neu definieren zu müssen, um ein ähnliches Verhalten zu erzielen.

Matthew Scharley
quelle

Antworten:

341

Nein, es gibt kein echtes Äquivalent zu typedef. Sie können 'using'-Anweisungen in einer Datei verwenden, z

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

Dies wirkt sich jedoch nur auf diese Quelldatei aus. In C und C ++ wird meiner Erfahrung nach typedefnormalerweise in .h-Dateien verwendet, die weit verbreitet sind. Daher typedefkann eine einzelne Datei für ein ganzes Projekt verwendet werden. Diese Fähigkeit ist in C # nicht vorhanden, da es #includein C # keine Funktionalität gibt, mit der Sie die usingAnweisungen aus einer Datei in eine andere aufnehmen können.

Zum Glück ist das Beispiel geben Sie tut ein Update haben - implizite Methode Gruppenumwandlung. Sie können Ihre Event-Abonnementzeile in Folgendes ändern:

gcInt.MyEvent += gcInt_MyEvent;

:) :)

Jon Skeet
quelle
11
Ich vergesse immer, dass du das kannst. Vielleicht, weil Visual Studio die ausführlichere Version vorschlägt. Aber ich kann TAB zweimal drücken, anstatt den
Handlernamen einzugeben
11
Nach meiner Erfahrung (die selten ist) müssen Sie den vollständig qualifizierten Typnamen angeben, zum Beispiel: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; Ist er korrekt? Ansonsten scheint es die darüber liegenden usingDefinitionen nicht zu berücksichtigen .
Tunnuz
3
Ich konnte nicht typedef uint8 myuuid[16];durch die Direktive "using" konvertieren . using myuuid = Byte[16];kompiliert nicht. usingkann nur zum Erstellen von Typ- Aliasen verwendet werden. typedefscheint viel flexibler zu sein, da es einen Alias ​​für eine ganze Deklaration (einschließlich Arraygrößen) erstellen kann. Gibt es in diesem Fall eine Alternative?
Natenho
2
@natenho: Nicht wirklich. Das nächste, was Sie kommen könnten, wäre wahrscheinlich eine Struktur mit einem Puffer fester Größe.
Jon Skeet
1
@tunnuz Es sei denn, Sie geben es in einem Namespace an
John Smith
38

Jon hat wirklich eine schöne Lösung gegeben, ich wusste nicht, dass du das kannst!

Manchmal habe ich von der Klasse geerbt und ihre Konstruktoren erstellt. Z.B

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

Nicht die beste Lösung (es sei denn, Ihre Baugruppe wird von anderen Personen verwendet), aber sie funktioniert.

Jonathan C Dickinson
quelle
41
Auf jeden Fall eine gute Methode, aber denken Sie daran, dass es diese (nervigen) versiegelten Typen gibt und sie dort nicht funktionieren. Ich wünschte wirklich, C # würde bereits typedefs einführen. Es ist ein verzweifeltes Bedürfnis (besonders für C ++ - Programmierer).
MasterMastic
1
Ich habe für diese Situation ein Projekt namens LikeType erstellt, das den zugrunde liegenden Typ umschließt, anstatt von ihm zu erben. Es wird auch implizit konvertieren TO den zugrunde liegenden Typ, so dass Sie so etwas wie verwenden könnten public class FooList : LikeType<IReadOnlyList<Foo>> { ... }und dann verwenden , überall würden Sie erwarten IReadOnlyList<Foo>. Meine Antwort unten zeigt mehr Details.
Matt Klein
3
Es wird auch nicht auf den Typ geschlossen, Foowenn er an eine Vorlagenmethode übergeben wird, die akzeptiert List<T>. Mit richtigem typedef wäre es möglich.
Aleksei Petrenko
18

Wenn Sie wissen, was Sie tun, können Sie eine Klasse mit impliziten Operatoren definieren, die zwischen der Alias-Klasse und der tatsächlichen Klasse konvertiert werden sollen.

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 };
    }
}

Ich unterstütze dies nicht wirklich und habe so etwas noch nie benutzt, aber dies könnte wahrscheinlich unter bestimmten Umständen funktionieren.

palswim
quelle
Danke @palswim, ich bin hierher gekommen und habe nach etwas wie "typedef string Identifier" gesucht. Ihr Vorschlag könnte also genau das sein, was ich brauche.
Jojo
6

C # unterstützt eine geerbte Kovarianz für Ereignisdelegierte, daher eine Methode wie die folgende:

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

Kann verwendet werden, um Ihre Veranstaltung zu abonnieren, ohne dass eine explizite Besetzung erforderlich ist

gcInt.MyEvent += LowestCommonHander;

Sie können sogar die Lambda-Syntax verwenden und die Intellisense wird alles für Sie erledigt:

gcInt.MyEvent += (sender, e) =>
{
    e. //you'll get correct intellisense here
};
Keith
quelle
Ich muss mich ernsthaft darum kümmern, einen guten Blick auf Linq zu werfen ... für den Rekord, ich habe damals für 2.0 gebaut (allerdings in VS 2008)
Matthew Scharley
Oh, auch, ich kann gut abonnieren, aber um zu den Ereignisargumenten zu gelangen, brauche ich eine explizite Besetzung und vorzugsweise einen Überprüfungscode, um auf der sicheren Seite zu sein.
Matthew Scharley
9
Die Syntax ist korrekt, aber ich würde nicht sagen, dass es "Linq-Syntax" ist. Vielmehr ist es ein Lambda-Ausdruck. Lambdas sind eine unterstützende Funktion, mit der Linq funktioniert, die jedoch völlig unabhängig davon sind. Grundsätzlich können Sie überall dort, wo Sie einen Delegaten verwenden können, einen Lambda-Ausdruck verwenden.
Scott Dorman
Fairer Punkt, ich hätte Lambda sagen sollen. Ein Delegat würde in .Net 2 arbeiten, aber Sie müssten den verschachtelten generischen Typ erneut explizit deklarieren.
Keith
5

Ich denke, es gibt kein typedef. Sie können nur einen bestimmten Delegatentyp anstelle des generischen in der GenericClass definieren, d. H.

public delegate GenericHandler EventHandler<EventData>

Dies würde es kürzer machen. Aber was ist mit dem folgenden Vorschlag:

Verwenden Sie Visual Studio. Auf diese Weise, wenn Sie tippten

gcInt.MyEvent += 

Es enthält bereits die vollständige Signatur des Ereignishandlers von Intellisense. Drücken Sie die Tabulatortaste und es ist da. Akzeptieren Sie den generierten Handlernamen oder ändern Sie ihn, und drücken Sie erneut die Tabulatortaste, um den Handler-Stub automatisch zu generieren.

OregonGhost
quelle
2
Ja, das habe ich getan, um das Beispiel zu generieren. Aber zurück zu kommen, um es noch einmal zu betrachten, nachdem die Tatsache immer noch verwirrend sein kann.
Matthew Scharley
Ich weiß, was du meinst. Aus diesem Grund möchte ich meine Ereignissignaturen kurz halten oder mich von der FxCop-Empfehlung entfernen, anstelle meines eigenen Delegatentyps Generic EventHandler <T> zu verwenden. Aber dann bleiben Sie bei der Kurzversion von Jon Skeet :)
OregonGhost
2
Wenn Sie ReSharper haben, wird Ihnen mitgeteilt, dass die lange Version übertrieben ist (indem Sie sie grau färben), und Sie können eine "Schnellkorrektur" verwenden, um sie wieder zu entfernen.
Roger Lipscombe
5

Sowohl in C ++ als auch in C # fehlen einfache Möglichkeiten zum Erstellen eines neuen Typs, der semantisch mit einem vorhandenen Typ identisch ist. Ich finde solche 'typedefs' für die typsichere Programmierung absolut notwendig und es ist eine echte Schande, dass c # sie nicht eingebaut hat. Der Unterschied zwischen void f(string connectionID, string username)to void f(ConID connectionID, UserName username)ist offensichtlich ...

(Sie können etwas Ähnliches in C ++ mit Boost in BOOST_STRONG_TYPEDEF erreichen.)

Es mag verlockend sein, Vererbung zu verwenden, aber das hat einige wesentliche Einschränkungen:

  • Es funktioniert nicht für primitive Typen
  • Der abgeleitete Typ kann weiterhin in den ursprünglichen Typ umgewandelt werden, dh wir können ihn an eine Funktion senden, die unseren ursprünglichen Typ empfängt. Dies macht den gesamten Zweck zunichte
  • Wir können nicht von versiegelten Klassen ableiten (und dh viele .NET-Klassen sind versiegelt).

Die einzige Möglichkeit, in C # etwas Ähnliches zu erreichen, besteht darin, unseren Typ in einer neuen Klasse zusammenzustellen:

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();
}

Während dies funktioniert, ist es nur für ein typedef sehr ausführlich. Außerdem haben wir ein Problem mit der Serialisierung (dh mit Json), da wir die Klasse über ihre Composed-Eigenschaft serialisieren möchten.

Unten finden Sie eine Hilfsklasse, die das "Curiously Recurring Template Pattern" verwendet, um dies viel einfacher zu machen:

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);
    }
  }

}

Mit Composer wird die obige Klasse einfach:

sealed Class SomeTypeTypeDef : Composer<SomeTypeTypeDef, SomeType> {
   public SomeTypeTypeDef(SomeType composed) : base(composed) {}

   // proxy the methods we want
   public void Method() => Composed.Method();
}

Außerdem wird der SomeTypeTypeDefWille auf die gleiche Weise SomeTypewie bei Json serialisiert .

Hoffe das hilft !

Kofifus
quelle
4

Sie können eine Open-Source-Bibliothek und ein NuGet-Paket namens LikeType verwenden , die ich erstellt habe und die Ihnen das gewünschteGenericClass<int> Verhalten verleihen .

Der Code würde folgendermaßen aussehen:

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));
    }
}
Matt Klein
quelle
Würde LikeType für etwas Komplexes wie stackoverflow.com/questions/50404586/… funktionieren ? Ich habe versucht, damit zu spielen, und kann kein funktionierendes Klassen-Setup erhalten.
Jay Croghan
Das ist nicht wirklich die Absicht der LikeTypeBibliothek. LikeTypeDer Hauptzweck des Unternehmens besteht darin, bei der primitiven Besessenheit zu helfen. Daher möchten Sie nicht, dass Sie den umschlossenen Typ so weitergeben können, als wäre es der Wrapper-Typ. Wie in, wenn ich Age : LikeType<int>dann mache, wenn meine Funktion eine benötigt Age, möchte ich sicherstellen, dass meine Anrufer eine übergeben Age, keine int.
Matt Klein
Davon abgesehen denke ich, dass ich eine Antwort auf Ihre Frage habe, die ich dort posten werde.
Matt Klein
3

Hier ist der Code dafür, viel Spaß! Ich habe aus der dotNetReference-Anweisung die Anweisung "using" in der Namespace-Zeile 106 http://referencesource.microsoft.com/#mscorlib/microsoft/win32/win32native.cs übernommen

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"};
        }
    }
}
shakram02
quelle
2

Für nicht versiegelte Klassen erben Sie einfach von ihnen:

public class Vector : List<int> { }

Für versiegelte Klassen ist es jedoch möglich, typedef-Verhalten mit einer solchen Basisklasse zu simulieren:

public abstract class Typedef<T, TDerived> where TDerived : Typedef<T, TDerived>, new()
{
    private T _value;

    public static implicit operator T(Typedef<T, TDerived> t)
    {
        return t == null ? default : t._value;
    }

    public static implicit operator Typedef<T, TDerived>(T t)
    {
        return t == null ? default : new TDerived { _value = t };
    }
}

// Usage examples

class CountryCode : Typedef<string, CountryCode> { }
class CurrencyCode : Typedef<string, CurrencyCode> { }
class Quantity : Typedef<int, Quantity> { }

void Main()
{
    var canadaCode = (CountryCode)"CA";
    var canadaCurrency = (CurrencyCode)"CAD";
    CountryCode cc = canadaCurrency;        // Compilation error
    Concole.WriteLine(canadaCode == "CA");  // true
    Concole.WriteLine(canadaCurrency);      // CAD

    var qty = (Quantity)123;
    Concole.WriteLine(qty);                 // 123
}
Vlad Rudenko
quelle
1

Die beste Alternative zu der typedef, die ich in C # gefunden habe, ist using. Zum Beispiel kann ich die Float-Genauigkeit über Compiler-Flags mit diesem Code steuern:

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

Leider ist es erforderlich, dass Sie dies oben in jeder Datei platzieren, in der Sie es verwenden real_t. Derzeit gibt es keine Möglichkeit, einen globalen Namespace-Typ in C # zu deklarieren.

Aaron Franke
quelle