C # 'da typedef eşdeğeri


326

C # 'da typedef eşdeğeri veya benzer bir davranış elde etmek için bir yol var mı? Biraz googling yaptım, ama baktığım her yer olumsuz görünüyor. Şu anda aşağıdakine benzer bir durum var:

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

Şimdi, bu olay için bir işleyici uygulamaya çalışırken bunun çok hızlı bir şekilde çok fazla yazmaya (korkunç cinayet için özür) yol açabileceğini anlamak bir roket bilimcisi gerektirmez. Sonuçta şöyle bir şey olurdu:

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

Dışında, benim durumumda, sadece int değil, karmaşık bir tür kullanıyordum. Bunu biraz basitleştirmek mümkün olsaydı iyi olurdu ...

Düzenleme: ie. belki de benzer davranışı elde etmek için yeniden tanımlamak yerine EventHandler türünü tanımlamak.

Yanıtlar:


341

Hayır, typedef'in gerçek bir eşdeğeri yoktur. Tek bir dosyada 'kullanma' yönergelerini kullanabilirsiniz, ör.

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

ancak bu yalnızca bu kaynak dosyayı etkiler. C ve C ++ 'da benim tecrübelerim typedefgenellikle yaygın olarak dahil edilen .h dosyalarında kullanılır - böylece tek typedefbir proje üzerinde kullanılabilir. Bu yetenek C # 'da mevcut değildir, çünkü #includeC # using' da bir dosyadaki yönergeleri başka bir dosyaya eklemenize izin verecek hiçbir işlevsellik yoktur .

Neyse ki, vermek örnek yapar örtülü yöntemi grup dönüştürme - bir düzeltme var. Etkinlik abonelik satırınızı şu şekilde değiştirebilirsiniz:

gcInt.MyEvent += gcInt_MyEvent;

:)


11
Bunu yapabileceğinizi daima unutuyorum. Belki de Visual Studio daha ayrıntılı bir sürüm önerdiğinden. Ama işleyici adını yazmak yerine iki kez SEKME tuşuna basmakla iyiyim;)
OregonGhost

11
Deneyimlerime göre (ki bu nadirdir), tam nitelikli tür adını belirtmeniz gerekir, örneğin: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; Doğru mu? Aksi takdirde, usingüstündeki tanımları dikkate almıyor gibi görünüyor .
tunnuz

3
typedef uint8 myuuid[16];"Using" yönergesini kullanarak dönüştüremedim . using myuuid = Byte[16];derlemez. usingyalnızca tür takma adları oluşturmak için kullanılabilir . typedefçok daha esnek görünmektedir, çünkü tüm bir bildirim için bir takma ad oluşturabilir (dizi boyutları dahil). Bu durumda başka alternatif var mı?
natenho

2
@natenho: Pek değil. Olabileceğiniz en yakın şey, muhtemelen sabit boyutlu bir tamponu olan bir yapıya sahip olmak olacaktır.
Jon Skeet

1
@tunnuz Bir ad alanı içinde belirtmedikçe
John Smith

38

Jon gerçekten güzel bir çözüm verdi, bunu yapabileceğini bilmiyordum!

Zaman zaman başvurduğum şey sınıftan miras alıp kurucularını yaratmaktı. Örneğin

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

En iyi çözüm değil (montajınız başkaları tarafından kullanılmadıkça), ama işe yarıyor.


41
Kesinlikle iyi bir yöntem, ancak bu (sinir bozucu) mühürlü türlerin var olduğunu ve orada çalışmayacağını unutmayın. Gerçekten C # typedefs tanıtmak isterdim. Bu umutsuz bir ihtiyaç (özellikle C ++ programcıları için).
MasterMastic

1
LikeType adlı bu durum için miras almak yerine altta yatan türü saran bir proje oluşturdum. Ayrıca örtülü olarak altta yatan tür TO dönüştürmek , böylece gibi bir şey kullanmak public class FooList : LikeType<IReadOnlyList<Foo>> { ... }ve daha sonra beklediğiniz her yerde kullanabilirsiniz IReadOnlyList<Foo>. Aşağıdaki cevabım daha fazla ayrıntı gösteriyor.
Matt Klein

3
Ayrıca Foo, örneğin kabul eden şablon yöntemine iletilirse türü çıkarmaz List<T>. Doğru typedef ile bu mümkün olacaktır.
Aleksei Petrenko

18

Ne yaptığınızı biliyorsanız, alias sınıfı ile gerçek sınıf arasında dönüştürmek için örtük işleçleri olan bir sınıf tanımlayabilirsiniz.

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

Aslında bunu onaylamıyorum ve daha önce böyle bir şey kullanmadım, ancak bu muhtemelen bazı özel durumlar için işe yarayabilir.


Teşekkürler @palswim, buraya "typedef string Identifier;" yani öneriniz tam da ihtiyacım olan şey olabilir.
yoyo

6

C #, olay temsilcileri için devralınan bir kovaryans destekler, bu nedenle şöyle bir yöntem:

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

Etkinliğinize abone olmak için kullanılabilir, açık bir yayın gerekmez

gcInt.MyEvent += LowestCommonHander;

Lambda sözdizimini bile kullanabilirsiniz ve intellisense sizin için yapılacaktır:

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

Ciddi Linq iyi bir göz atmak için dolaşmak gerekiyor ... kayıt için olsa da, ben o zaman 2.0 için bina (VS 2008 olsa da)
Matthew Scharley

Oh, ayrıca, iyi abone olabilirim, ama sonra olay argümanlarına ulaşmak için açık bir oyuncuya ihtiyacım var ve tercihen sadece güvenli tarafta olmak için kontrol kodunu yazın.
Matthew Scharley

9
Sözdizimi doğru, ama bunun "Linq sözdizimi" olduğunu söyleyemem; daha ziyade bir lambda ifadesidir. Lambdas, Linq'i çalıştıran destekleyici bir özelliktir, ancak tamamen bağımsızdır. Esasen, bir temsilci kullanabileceğiniz her yerde bir lambda ifadesi kullanabilirsiniz.
Scott Dorman

Adil nokta, lambda demeliydim. Bir temsilci .Net 2'de çalışır, ancak iç içe genel türü yeniden açıkça bildirmeniz gerekir.
Keith

5

Bence typedef yok. GenericClass içindeki genel yerine yalnızca belirli bir temsilci türü tanımlayabilirsiniz.

public delegate GenericHandler EventHandler<EventData>

Bu onu kısaltır. Ancak aşağıdaki öneriye ne dersiniz:

Visual Studio'yu kullanın. Bu şekilde, yazdığınızda

gcInt.MyEvent += 

Intellisense'in tam olay işleyicisi imzasını zaten sağlıyor. SEKME tuşuna basın ve orada. Oluşturulan işleyici adını kabul edin veya değiştirin ve işleyici saplamasını otomatik olarak oluşturmak için SEKME tuşuna tekrar basın.


2
Evet, örnek oluşturmak için bunu yaptım. Ama gerçeği yine de kafa karıştırıcı olabilir SONRA tekrar bakmak için geri geliyor.
Matthew Scharley

Ne demek istediğini biliyorum. Bu nedenle etkinlik imzalarımı kısa tutmayı veya kendi temsilci türüm yerine Genel EventHandler <T> kullanma FxCop önerisinden uzaklaşmayı seviyorum. Ama sonra, Jon Skeet tarafından sağlanan kısa el versiyonuyla sopa :)
OregonGhost

2
ReSharper'ınız varsa, uzun versiyonun aşırı dolu olduğunu (gri renkte boyayarak) söyleyecektir ve tekrar kurtulmak için "hızlı bir düzeltme" kullanabilirsiniz.
Roger Lipscombe

5

Hem C ++ hem de C # mevcut bir türle semantik olarak aynı olan yeni bir tür oluşturmanın kolay yollarını kaçırmaktadır . Bu tür 'typedefs' türünü güvenli programlama için tamamen gerekli buluyorum ve gerçek bir utanç c # yerleşik değildir. Arasındaki fark void f(string connectionID, string username)içinvoid f(ConID connectionID, UserName username) açıktır ...

(C ++ 'da benzer bir şeyi BOOST_STRONG_TYPEDEF'te artırarak elde edebilirsiniz)

Kalıtım kullanmak cazip gelebilir, ancak bazı büyük sınırlamaları vardır:

  • ilkel tipler için çalışmaz
  • türetilmiş tip hala orijinal türe dökülebilir, yani orijinal tipimizi alan bir fonksiyona gönderebiliriz, bu tüm amacı yener
  • kapalı sınıflardan türeyemeyiz (ve birçok .NET sınıfı kapalıdır)

C # 'da benzer bir şeyi elde etmenin tek yolu, türümüzü yeni bir sınıfta oluşturmaktır:

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

Bu işe yarayacak olsa da, sadece bir typedef için çok ayrıntılı. Ayrıca, sınıfın Composed özelliği aracılığıyla serileştirmek istediğimiz için serileştirme (yani Json'a) ile ilgili bir sorunumuz var.

Aşağıda bunu daha basit hale getirmek için "Merakla Yinelenen Şablon Kalıbı" kullanan bir yardımcı sınıf bulunmaktadır:

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

}

Composer ile yukarıdaki sınıf basitçe:

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

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

Buna ek olarak, SomeTypeTypeDefirade aynı şekilde Json'aSomeType .

Bu yardımcı olur umarım !


4

Oluşturduğum LikeType adlı bir açık kaynak kitaplığı ve NuGet paketi kullanabilirsiniz.GenericClass<int>Aradığın davranışı .

Kod şöyle görünecektir:

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, stackoverflow.com/questions/50404586/… gibi karmaşık bir şey için çalışır mı? Onunla oynamaya çalıştım ve çalışan bir sınıf kurulumu alamıyorum.
Jay Croghan

Bu gerçekten LikeTypekütüphanenin amacı değil . LikeTypeBirincil amacı, İlkel Saplantıya yardımcı olmaktır ve bu nedenle, sarılmış türün etrafını sarıcı türü gibi geçirebilmenizi istemez. Olduğu gibi, eğer yaparsam, Age : LikeType<int>fonksiyonumun bir ihtiyacı varsa Age, arayanlarımın bir Agedeğil, bir geçirdiğinden emin olmak istiyorum int.
Matt Klein

Bununla birlikte, sorunuzu cevaplayacağım, orada göndereceğim.
Matt Klein

3

İşte bunun için kod, tadını çıkarın !, dotNetReference türünden 106 ad alanı satırındaki "using" ifadesini aldım 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

Mühürlü olmayan sınıflar için onlardan miras alın:

public class Vector : List<int> { }

Ancak mühürlü sınıflar için böyle bir temel sınıfla typedef davranışını simüle etmek mümkündür:

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

typedefC # bulduğum en iyi alternatif using. Örneğin, bu kodla derleyici bayrakları aracılığıyla şamandıra hassasiyetini kontrol edebilirim:

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

Ne yazık ki, bunu kullandığınız her dosyanın üstüne yerleştirmenizi gerektirir real_t. Şu anda C # 'da genel bir ad alanı türü bildirmenin bir yolu yoktur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.