Setara dengan typedef dalam C #

326

Apakah ada setara typedef dalam C #, atau cara lain untuk mendapatkan semacam perilaku yang serupa? Saya telah melakukan beberapa googling, tetapi di mana-mana saya kelihatannya negatif. Saat ini saya memiliki situasi yang mirip dengan yang berikut:

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

Sekarang, tidak perlu ilmuwan roket untuk mengetahui bahwa ini dapat dengan cepat menyebabkan banyak pengetikan (permintaan maaf untuk permainan kata-kata yang mengerikan) ketika mencoba menerapkan penangan untuk acara itu. Itu akan berakhir seperti ini:

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

Kecuali, dalam kasus saya, saya sudah menggunakan tipe yang kompleks, bukan hanya int. Akan lebih baik jika memungkinkan untuk menyederhanakan ini sedikit ...

Edit: mis. mungkin mengetikkan EventHandler daripada perlu mendefinisikannya kembali untuk mendapatkan perilaku yang sama.

Matthew Scharley
sumber

Jawaban:

341

Tidak, tidak ada padanan sebenarnya dari typedef. Anda dapat menggunakan arahan 'menggunakan' dalam satu file, mis

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

tapi itu hanya akan berdampak pada file sumber itu. Dalam C dan C ++, pengalaman saya adalah yang typedefbiasanya digunakan dalam file .h yang disertakan secara luas - sehingga satu typedefdapat digunakan di seluruh proyek. Kemampuan itu tidak ada di C #, karena tidak ada #includefungsi di C # yang akan memungkinkan Anda untuk memasukkan usingarahan dari satu file ke file lain.

Untungnya, contoh yang Anda berikan memang memiliki konversi grup metode fix - implisit. Anda dapat mengubah jalur berlangganan acara menjadi hanya:

gcInt.MyEvent += gcInt_MyEvent;

:)

Jon Skeet
sumber
11
Saya selalu lupa bahwa Anda bisa melakukan ini. Mungkin karena Visual Studio menyarankan versi yang lebih verbose. Tapi saya baik-baik saja dengan menekan TAB dua kali daripada mengetik nama penangan;)
OregonGhost
11
Dalam pengalaman saya (yang langka), Anda harus menentukan nama jenis yang memenuhi syarat, misalnya: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; Apakah itu benar? Kalau tidak, tampaknya tidak mempertimbangkan usingdefinisi di atasnya.
tunnuz
3
Saya tidak dapat mengonversi typedef uint8 myuuid[16];melalui "menggunakan" arahan. using myuuid = Byte[16];tidak dikompilasi. usingdapat digunakan hanya untuk membuat alias tipe . typedeftampaknya jauh lebih fleksibel, karena dapat membuat alias untuk seluruh deklarasi (termasuk ukuran array). Apakah ada alternatif dalam hal ini?
natenho
2
@natenho: Tidak juga. Mungkin yang paling dekat dengan Anda adalah memiliki struct dengan buffer ukuran tetap, mungkin.
Jon Skeet
1
@tunnuz Kecuali Anda menentukannya di dalam namespace
John Smith
38

Jon benar-benar memberikan solusi yang bagus, saya tidak tahu Anda bisa melakukannya!

Kadang saya terpaksa mewarisi dari kelas dan menciptakan konstruktornya. Misalnya

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

Bukan solusi terbaik (kecuali majelis Anda digunakan oleh orang lain), tetapi berhasil.

Jonathan C Dickinson
sumber
41
Jelas metode yang bagus, tetapi perlu diingat bahwa tipe-tipe tersegel (menjengkelkan) itu ada, dan itu tidak akan berfungsi di sana. Saya sangat berharap C # akan memperkenalkan typedefs sudah. Ini adalah kebutuhan yang mendesak (terutama untuk programmer C ++).
MasterMastic
1
Saya telah membuat proyek untuk situasi ini yang disebut LikeType yang membungkus tipe yang mendasarinya daripada mewarisi darinya. Ini juga akan secara implisit mengkonversi ke jenis yang mendasari, sehingga Anda bisa menggunakan sesuatu seperti public class FooList : LikeType<IReadOnlyList<Foo>> { ... }dan kemudian menggunakannya di mana saja Anda akan mengharapkan IReadOnlyList<Foo>. Jawaban saya di bawah ini menunjukkan lebih detail.
Matt Klein
3
Itu juga tidak akan menyimpulkan jenis Foojika diteruskan ke mis. Metode templat yang menerima List<T>. Dengan mengetikkan yang tepat itu akan mungkin.
Aleksei Petrenko
18

Jika Anda tahu apa yang Anda lakukan, Anda dapat mendefinisikan kelas dengan operator implisit untuk mengkonversi antara kelas alias dan kelas aktual.

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

Saya sebenarnya tidak mendukung ini dan belum pernah menggunakan sesuatu seperti ini, tetapi ini mungkin bisa bekerja untuk beberapa keadaan tertentu.

palswim
sumber
Terima kasih @alswim, saya tiba di sini mencari sesuatu seperti "typedef string Identifier;" jadi saran Anda mungkin hanya apa yang saya butuhkan.
yoyo
6

C # mendukung beberapa kovarian bawaan untuk delegasi acara, jadi metode ini seperti ini:

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

Dapat digunakan untuk berlangganan acara Anda, tidak ada pemeran eksplisit yang diperlukan

gcInt.MyEvent += LowestCommonHander;

Anda bahkan dapat menggunakan sintaks lambda dan intellisense akan dilakukan untuk Anda:

gcInt.MyEvent += (sender, e) =>
{
    e. //you'll get correct intellisense here
};
Keith
sumber
Saya benar-benar perlu menyiasati agar bisa melihat Linq dengan baik ... sebagai catatan, saya sedang membangun untuk 2.0 pada saat itu (dalam VS 2008)
Matthew Scharley
Oh, juga, saya bisa berlangganan dengan baik, tapi kemudian untuk mendapatkan argumen acara saya perlu pemeran eksplisit, dan lebih baik ketik kode pengecekan, hanya untuk berada di sisi yang aman.
Matthew Scharley
9
Sintaksnya benar, tetapi saya tidak akan mengatakan itu "Sintaks Linq"; lebih tepatnya itu adalah ekspresi lambda. Lambdas adalah fitur pendukung yang membuat Linq berfungsi, tetapi sepenuhnya independen darinya. Intinya, di mana saja Anda bisa menggunakan delegasi, Anda bisa menggunakan ekspresi lambda.
Scott Dorman
Poin yang wajar, saya seharusnya mengatakan lambda. Delegasi akan bekerja di .Net 2, tetapi Anda harus secara eksplisit mendeklarasikan tipe generik bersarang lagi.
Keith
5

Saya pikir tidak ada typedef. Anda hanya bisa mendefinisikan tipe delegasi tertentu daripada yang umum di GenericClass, yaitu

public delegate GenericHandler EventHandler<EventData>

Ini akan membuatnya lebih pendek. Tetapi bagaimana dengan saran berikut:

Gunakan Visual Studio. Dengan cara ini, saat Anda mengetik

gcInt.MyEvent += 

itu sudah menyediakan tanda tangan event handler yang lengkap dari Intellisense. Tekan TAB dan itu ada di sana. Terima nama pawang yang dihasilkan atau ubah, lalu tekan TAB lagi untuk menghasilkan puntung rintisan secara otomatis.

OregonGhost
sumber
2
Ya, itulah yang saya lakukan untuk menghasilkan contoh. Tetapi kembali untuk melihatnya lagi SETELAH faktanya masih bisa membingungkan.
Matthew Scharley
Aku tahu apa yang kamu maksud. Itu sebabnya saya ingin membuat tanda tangan acara saya singkat, atau menjauh dari rekomendasi FxCop untuk menggunakan Generic EventHandler <T> bukan tipe delegasi saya sendiri. Tapi kemudian, tetap dengan versi tangan pendek yang disediakan oleh Jon Skeet :)
OregonGhost
2
Jika Anda memiliki ReSharper, itu akan memberi tahu Anda bahwa versi panjangnya berlebihan (dengan mewarnainya menjadi abu-abu), dan Anda dapat menggunakan "perbaikan cepat" untuk menghilangkannya lagi.
Roger Lipscombe
5

Baik C ++ dan C # tidak ada cara mudah untuk membuat tipe baru yang secara semantik identik dengan tipe yang ada. Saya menemukan 'typedef' seperti itu benar-benar penting untuk pemrograman tipe-aman dan itu memalukan c # tidak memilikinya Perbedaan antara void f(string connectionID, string username)ke void f(ConID connectionID, UserName username)jelas ...

(Anda dapat mencapai hal serupa di C ++ dengan meningkatkan di BOOST_STRONG_TYPEDEF)

Mungkin tergoda untuk menggunakan warisan tetapi itu memiliki beberapa batasan utama:

  • itu tidak akan bekerja untuk tipe primitif
  • tipe turunan masih dapat dicasting ke tipe asli, yaitu kita dapat mengirimkannya ke fungsi yang menerima tipe asli kita, ini mengalahkan seluruh tujuan
  • kita tidak dapat berasal dari kelas yang disegel (dan mis. banyak kelas .NET disegel)

Satu-satunya cara untuk mencapai hal serupa di C # adalah dengan menyusun tipe kami di kelas baru:

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

Meskipun ini akan berhasil, sangat verbose hanya untuk typedef. Selain itu kami memiliki masalah dengan serialisasi (yaitu untuk Json) karena kami ingin membuat cerita bersambung kelas melalui properti Composed-nya.

Di bawah ini adalah kelas pembantu yang menggunakan "Curiously Recurring Pattern Pattern" untuk membuatnya lebih sederhana:

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

}

Dengan Composer, kelas di atas menjadi sederhana:

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

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

Dan selain itu SomeTypeTypeDefakan bersambung ke Json dengan cara yang SomeTypesama.

Semoga ini membantu !

kofifus
sumber
4

Anda dapat menggunakan pustaka sumber terbuka dan paket NuGet bernama LikeType yang saya buat yang akan memberi Anda GenericClass<int>perilaku yang Anda cari.

Kode akan terlihat seperti:

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
sumber
akankah LikeType berfungsi untuk sesuatu yang kompleks seperti stackoverflow.com/questions/50404586/… ? Saya sudah mencoba bermain dengannya dan tidak bisa mendapatkan pengaturan kelas yang berfungsi.
Jay Croghan
Itu sebenarnya bukan maksud LikeTypeperpustakaan. LikeTypeTujuan utama adalah untuk membantu dengan Obsesi Primitif , dan karena itu, tidak ingin Anda dapat membagikan jenis yang dibungkus seperti itu adalah jenis pembungkus. Seperti pada, jika saya membuat Age : LikeType<int>maka jika fungsi saya membutuhkan Age, saya ingin memastikan penelepon saya melewati Age, bukan int.
Matt Klein
Yang sedang berkata, saya pikir saya punya jawaban untuk pertanyaan Anda, yang akan saya posting di sana.
Matt Klein
3

Ini kode untuk itu, selamat menikmati !, saya mengambilnya dari dotNetReference, ketik pernyataan "using" di dalam namespace line 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"};
        }
    }
}
shakram02
sumber
2

Untuk kelas yang tidak disegel cukup mewarisi dari mereka:

public class Vector : List<int> { }

Tetapi untuk kelas tertutup, dimungkinkan untuk mensimulasikan perilaku typedef dengan kelas dasar seperti itu:

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
sumber
1

Alternatif terbaik typedefyang saya temukan di C # adalah using. Sebagai contoh, saya dapat mengontrol presisi float melalui flag compiler dengan kode ini:

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

Sayangnya, Anda harus menempatkan ini di bagian atas setiap file tempat Anda menggunakan real_t. Saat ini tidak ada cara untuk mendeklarasikan tipe namespace global dalam C #.

Aaron Franke
sumber