Equivalente di typedef in C #


326

Esiste un equivalente dattiloscritto in C # o in qualche modo ottenere un comportamento simile? Ho fatto un po 'di googling, ma ovunque sembro essere negativo. Attualmente ho una situazione simile alla seguente:

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

Ora, non ci vuole uno scienziato missilistico per capire che questo può portare molto rapidamente a scrivere molto (scuse per il gioco di parole orribile) quando cerca di implementare un gestore per quell'evento. Sarebbe finito per essere qualcosa del genere:

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

Tranne, nel mio caso, stavo già usando un tipo complesso, non solo un int. Sarebbe bello se fosse possibile semplificarlo un po '...

Modifica: ad es. forse dattiloscrivere EventHandler invece di doverlo ridefinire per ottenere un comportamento simile.

Risposte:


341

No, non esiste un vero equivalente di typedef. È possibile utilizzare le direttive "utilizzo" all'interno di un file, ad es

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

ma ciò avrà un impatto solo sul file sorgente. In C e C ++, la mia esperienza è che di typedefsolito viene utilizzata all'interno di file .h che sono ampiamente inclusi, quindi un singolo typedefpuò essere utilizzato su un intero progetto. Questa capacità non esiste in C #, perché non esiste alcuna #includefunzionalità in C # che ti consenta di includere le usingdirettive da un file a un altro.

Per fortuna, l'esempio si dà ha un fix - Metodo di conversione del gruppo implicita. È possibile modificare la riga di iscrizione all'evento in solo:

gcInt.MyEvent += gcInt_MyEvent;

:)


11
Dimentico sempre che puoi farlo. Forse perché Visual Studio suggerisce la versione più dettagliata. Ma sto bene premendo due volte TAB invece di digitare il nome del gestore;)
OregonGhost

11
Nella mia esperienza (che è scarsa), devi specificare il nome del tipo completo, ad esempio: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; è corretto? Altrimenti non sembra prendere in considerazione le usingdefinizioni sopra di esso.
tunnuz,

3
Non sono riuscito a convertire typedef uint8 myuuid[16];tramite la direttiva "using". using myuuid = Byte[16];non compilare. usingpuò essere utilizzato solo per la creazione alias di tipo . typedefsembra essere molto più flessibile, poiché può creare un alias per un'intera dichiarazione (comprese le dimensioni dell'array). C'è qualche alternativa in questo caso?
natenho,

2
@natenho: Non proprio. Il più vicino che potresti venire sarebbe probabilmente avere una struttura con un buffer di dimensioni fisse, probabilmente.
Jon Skeet,

1
@tunnuz A meno che non lo specifichi all'interno di uno spazio dei nomi
John Smith

38

Jon ha davvero dato una bella soluzione, non sapevo che tu potessi farlo!

A volte ciò a cui ricorsi era ereditare dalla classe e creare i suoi costruttori. Per esempio

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

Non è la soluzione migliore (a meno che l'assembly non venga utilizzato da altre persone), ma funziona.


41
Sicuramente un buon metodo, ma tieni presente che esistono quei tipi (fastidiosi) sigillati e non funzionerà lì. Vorrei davvero che C # introducesse già typedefs. È un'esigenza disperata (specialmente per i programmatori C ++).
MasterMastic,

1
Ho creato un progetto per questa situazione chiamato LikeType che avvolge il tipo sottostante anziché ereditarlo. Sarà inoltre convertire implicitamente TO tipo sottostante, così si potrebbe usare qualcosa come public class FooList : LikeType<IReadOnlyList<Foo>> { ... }e quindi utilizzarlo ovunque ci si aspetterebbe una IReadOnlyList<Foo>. La mia risposta di seguito mostra ulteriori dettagli.
Matt Klein,

3
Inoltre non inferirà il tipo Foose passato ad esempio al metodo template che accetta List<T>. Con una corretta digitazione sarebbe possibile.
Aleksei Petrenko,

18

Se sai cosa stai facendo, puoi definire una classe con operatori impliciti per la conversione tra la classe alias e la classe effettiva.

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

In realtà non lo sostengo e non ho mai usato qualcosa del genere, ma questo potrebbe probabilmente funzionare in alcune circostanze specifiche.


Grazie @palswim, sono arrivato qui alla ricerca di qualcosa come "typedef string Identifier;" quindi il tuo suggerimento potrebbe essere proprio quello di cui ho bisogno.
yoyo

6

C # supporta alcune covarianze ereditate per i delegati di eventi, quindi un metodo come questo:

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

Può essere utilizzato per iscriversi al tuo evento, non è richiesto alcun cast esplicito

gcInt.MyEvent += LowestCommonHander;

Puoi anche usare la sintassi lambda e l'intellisense sarà tutto fatto per te:

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

Ho seriamente bisogno di andare in giro a dare una buona occhiata a Linq ... per la cronaca, al momento stavo costruendo per 2.0 (in VS 2008)
Matthew Scharley,

Oh, anche, posso iscrivermi bene, ma poi per ottenere gli argomenti dell'evento ho bisogno di un cast esplicito, e preferibilmente digitare il codice di controllo, solo per essere al sicuro.
Matthew Scharley,

9
La sintassi è corretta, ma non direi che è "sintassi Linq"; piuttosto è un'espressione lambda. Le lambda sono una funzione di supporto che fa funzionare Linq, ma ne sono completamente indipendenti. In sostanza, ovunque sia possibile utilizzare un delegato, è possibile utilizzare un'espressione lambda.
Scott Dorman,

Giusto punto, avrei dovuto dire lambda. Un delegato funzionerebbe in .Net 2, ma sarebbe necessario dichiarare di nuovo esplicitamente il tipo generico nidificato.
Keith,

5

Penso che non ci sia typedef. È possibile definire solo un tipo delegato specifico anziché quello generico nella GenericClass, ad es

public delegate GenericHandler EventHandler<EventData>

Ciò lo renderebbe più breve. Ma per quanto riguarda il seguente suggerimento:

Usa Visual Studio. In questo modo, quando hai digitato

gcInt.MyEvent += 

fornisce già la firma completa del gestore eventi da Intellisense. Premi TAB ed è lì. Accettare il nome del gestore generato o modificarlo, quindi premere nuovamente TAB per generare automaticamente lo stub del gestore.


2
Sì, è quello che ho fatto per generare l'esempio. Ma tornare a guardarlo di nuovo DOPO che il fatto può ancora essere fonte di confusione.
Matthew Scharley,

So cosa vuoi dire. Ecco perché mi piace mantenere le firme degli eventi brevi o allontanarmi dalla raccomandazione di FxCop di utilizzare Generic EventHandler <T> invece del mio tipo di delegato. Ma poi,
segui la

2
Se hai ReSharper, ti dirà che la versione lunga è eccessiva (colorandola in grigio) e puoi usare una "soluzione rapida" per sbarazzartene di nuovo.
Roger Lipscombe,

5

Sia in C ++ che in C # mancano semplici modi per creare un nuovo tipo semanticamente identico a un tipo esistente. Trovo che tali "typedef" siano assolutamente essenziali per una programmazione sicura ed è un vero peccato che c # non li abbia integrati. La differenza tra void f(string connectionID, string username)to void f(ConID connectionID, UserName username)è evidente ...

(Puoi ottenere qualcosa di simile in C ++ con boost in BOOST_STRONG_TYPEDEF)

Potrebbe essere allettante utilizzare l'ereditarietà ma ciò presenta alcuni limiti importanti:

  • non funzionerà per tipi primitivi
  • il tipo derivato può ancora essere trasmesso al tipo originale, ovvero possiamo inviarlo a una funzione che riceve il nostro tipo originale, questo vanifica l'intero scopo
  • non possiamo derivare da classi sigillate (e cioè molte classi .NET sono sigillate)

L'unico modo per ottenere qualcosa di simile in C # è comporre il nostro tipo in una nuova 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();
}

Mentre questo funzionerà, è molto prolisso solo per un typedef. Inoltre abbiamo un problema con la serializzazione (cioè con Json) in quanto vogliamo serializzare la classe attraverso la sua proprietà Composed.

Di seguito è riportata una classe di supporto che utilizza il "Modello di modello curiosamente ricorrente" per semplificare le cose:

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

}

Con Composer la classe sopra diventa semplicemente:

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

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

Inoltre SomeTypeTypeDef, serializzerà Json nello stesso modoSomeType .

Spero che questo ti aiuti !


4

Puoi usare una libreria open source e un pacchetto NuGet chiamato LikeType che ho creato che ti daràGenericClass<int> comportamento che stai cercando.

Il codice sarebbe simile a:

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

LikeType funzionerebbe per qualcosa di complesso come stackoverflow.com/questions/50404586/… ? Ho provato a giocarci e non riesco a ottenere una configurazione di classe che funzioni.
Jay Croghan,

Questo non è proprio lo scopo della LikeTypebiblioteca. LikeTypeLo scopo principale è quello di aiutare con l' ossessione primitiva e, come tale, non vuole che tu sia in grado di passare attorno al tipo di involucro come se fosse il tipo di avvolgimento. Come in, se faccio Age : LikeType<int>allora se la mia funzione ha bisogno di un Age, voglio assicurarmi che i miei chiamanti stiano passando un Age, non un int.
Matt Klein,

Detto questo, penso di avere una risposta alla tua domanda, che posterò laggiù.
Matt Klein,

3

Ecco il codice per questo, buon divertimento !, L'ho preso da dotNetReference e digita l'istruzione "using" nella riga dello spazio dei nomi 106 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

Per le classi non sigillate è sufficiente ereditarle:

public class Vector : List<int> { }

Ma per le classi sigillate è possibile simulare il comportamento typedef con tale classe base:

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
}

1

La migliore alternativa a typedefquella che ho trovato in C # è using. Ad esempio, posso controllare la precisione del float tramite flag di compilatore con questo codice:

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

Sfortunatamente, è necessario posizionarlo nella parte superiore di ogni file in cui si utilizza real_t. Al momento non è possibile dichiarare un tipo di spazio dei nomi globale in C #.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.