เทียบเท่าของ typedef ใน C #


326

มีการพิมพ์ typedef ใน C # หรือบางครั้งเพื่อให้ได้ลักษณะคล้ายกัน? ฉันทำ Google เสร็จแล้ว แต่ดูเหมือนว่าทุกที่ฉันจะลบ ขณะนี้ฉันมีสถานการณ์คล้ายกับต่อไปนี้:

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

ตอนนี้มันไม่ได้ใช้นักวิทยาศาสตร์จรวดที่จะเข้าใจว่าสิ่งนี้สามารถนำไปสู่การพิมพ์จำนวนมากได้อย่างรวดเร็ว (ขอโทษสำหรับปุนน่ากลัว) เมื่อพยายามที่จะดำเนินการจัดการสำหรับเหตุการณ์ที่ มันจะกลายเป็นแบบนี้:

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

ยกเว้นในกรณีของฉันฉันใช้แบบซับซ้อนอยู่แล้วไม่ใช่แค่ int มันคงจะดีถ้ามันเป็นไปได้ที่จะทำให้สิ่งนี้ง่ายขึ้นเล็กน้อย ...

แก้ไข: เช่น อาจพิมพ์การกำหนด EventHandler แทนที่จะต้องกำหนดใหม่เพื่อให้ได้ลักษณะการทำงานที่คล้ายกัน

คำตอบ:


341

ไม่มันไม่เหมือน typedef ที่แท้จริง คุณสามารถใช้คำสั่ง 'using' ภายในหนึ่งไฟล์เช่น

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

แต่นั่นจะส่งผลกระทบกับไฟล์ต้นฉบับนั้นเท่านั้น ใน C และ C ++ ประสบการณ์ของฉันtypedefคือโดยทั่วไปจะใช้ภายในไฟล์. h ซึ่งรวมอยู่ในวงกว้าง - ดังนั้นจึงtypedefสามารถใช้งานเดี่ยวในโครงการทั้งหมด ความสามารถนั้นไม่มีอยู่ใน C # เนื่องจากไม่มี#includeฟังก์ชันการทำงานใน C # ที่จะอนุญาตให้คุณรวมusingคำสั่งจากไฟล์หนึ่งในอีกไฟล์หนึ่ง

โชคดีที่ตัวอย่างที่คุณให้นั้นมีการแปลงกลุ่มวิธีแก้ไขโดยปริยาย คุณสามารถเปลี่ยนสายการสมัครรับข้อมูลกิจกรรมของคุณเป็นเพียงแค่:

gcInt.MyEvent += gcInt_MyEvent;

:)


11
ฉันลืมเสมอว่าคุณสามารถทำได้ อาจเป็นเพราะ Visual Studio แนะนำรุ่นที่ละเอียดมากขึ้น แต่ฉันไม่เป็นไรถ้ากด TAB สองครั้งแทนที่จะพิมพ์ชื่อตัวจัดการ;)
OregonGhost

11
จากประสบการณ์ของฉัน (ซึ่งหายาก) คุณต้องระบุชื่อประเภทที่ผ่านการรับรองตัวอย่างเช่น: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; ถูกต้องหรือไม่ มิฉะนั้นจะไม่พิจารณาusingคำจำกัดความด้านบน
tunnuz

3
ฉันไม่สามารถแปลงtypedef uint8 myuuid[16];ผ่านคำสั่ง "ใช้" using myuuid = Byte[16];ไม่ได้รวบรวม usingสามารถใช้เพื่อสร้างนามแฝงประเภท typedefดูเหมือนว่าจะมีความยืดหยุ่นมากกว่านี้เนื่องจากสามารถสร้างนามแฝงสำหรับการประกาศทั้งหมด (รวมถึงขนาดอาร์เรย์) มีทางเลือกอื่นในกรณีนี้หรือไม่?
natenho

2
@natenho: ไม่จริง สิ่งที่ใกล้เคียงที่สุดที่คุณอาจจะมีคือโครงสร้างที่มีบัฟเฟอร์ขนาดคงที่
Jon Skeet

1
@tunnuz ยกเว้นคุณระบุไว้ในเนมสเปซ
John Smith

38

จอนให้ทางออกที่ดีจริงๆฉันไม่รู้ว่าคุณจะทำอย่างนั้นได้!

บางครั้งสิ่งที่ฉันหันไปใช้คือการสืบทอดจากชั้นเรียนและสร้างงานก่อสร้าง เช่น

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

ไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด (ยกเว้นกรณีที่การชุมนุมของคุณถูกใช้โดยผู้อื่น) แต่ใช้งานได้


41
วิธีการที่ดีแน่นอน แต่โปรดจำไว้ว่ามีประเภทผนึก (น่ารำคาญ) อยู่และมันจะไม่ทำงานที่นั่น ฉันหวังว่า C # จะแนะนำ typedefs อยู่แล้ว มันเป็นความต้องการที่หมดหวัง (โดยเฉพาะอย่างยิ่งสำหรับโปรแกรมเมอร์ C ++)
MasterMastic

1
ฉันได้สร้างโครงการสำหรับสถานการณ์นี้ที่ชื่อว่า LikeType ซึ่งล้อมรอบประเภทที่สำคัญแทนที่จะสืบทอดจากมัน นอกจากนี้ยังจะปริยายแปลงTOชนิดพื้นฐานเพื่อให้คุณสามารถใช้สิ่งที่ต้องการแล้วใช้งานได้ทุกที่คุณคาดหวังpublic class FooList : LikeType<IReadOnlyList<Foo>> { ... } คำตอบของฉันด้านล่างแสดงรายละเอียดเพิ่มเติม IReadOnlyList<Foo>
Matt Klein

3
นอกจากนี้ยังจะไม่สรุปชนิดถ้าผ่านไปยังวิธีการเช่นแม่แบบที่ยอมรับFoo List<T>ด้วยการพิมพ์ที่เหมาะสมมันจะเป็นไปได้
Aleksei Petrenko

18

หากคุณรู้ว่าคุณกำลังทำอะไรคุณสามารถกำหนดคลาสด้วยตัวดำเนินการโดยปริยายเพื่อแปลงระหว่างคลาสนามแฝงและคลาสจริง

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

ฉันไม่ได้รับรองสิ่งนี้จริง ๆ และไม่เคยใช้อะไรแบบนี้มาก่อน


ขอบคุณ @palswim ฉันมาที่นี่เพื่อค้นหาบางสิ่งเช่น "typedef string Identifier;" ดังนั้นคำแนะนำของคุณอาจเป็นสิ่งที่ฉันต้องการ
yoyo

6

C # รองรับความแปรปรวนร่วมที่สืบทอดมาสำหรับผู้ได้รับมอบหมายเหตุการณ์ดังนั้นวิธีการเช่นนี้:

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

สามารถใช้เพื่อสมัครรับข้อมูลกิจกรรมของคุณโดยไม่ต้องใช้ตัวเลือกที่ชัดเจน

gcInt.MyEvent += LowestCommonHander;

คุณสามารถใช้แลมบ์ดาไวยากรณ์และ Intellisense จะทำเพื่อคุณ:

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

ฉันต้องจริงจังที่จะได้ดู Linq ... สำหรับการบันทึก แต่ฉันกำลังสร้าง 2.0 ในขณะนั้น (ใน VS 2008 แม้ว่า)
Matthew Scharley

โอ้นอกจากนี้ฉันสามารถสมัครสมาชิกได้ดี แต่จากนั้นจะได้รับการโต้แย้งเหตุการณ์ที่ฉันต้องการนักแสดงที่ชัดเจนและพิมพ์รหัสตรวจสอบโดยเฉพาะอย่างยิ่งที่จะอยู่ในด้านความปลอดภัย
Matthew Scharley

9
ไวยากรณ์ถูกต้อง แต่ฉันจะไม่บอกว่ามันคือ "Linq syntax"; ค่อนข้างเป็นการแสดงออกแลมบ์ดา Lambdas เป็นคุณสมบัติสนับสนุนที่ทำให้ Linq ทำงานได้ แต่ไม่ขึ้นอยู่กับมัน เป็นหลักทุกที่ที่คุณสามารถใช้ผู้รับมอบสิทธิ์คุณสามารถใช้แลมบ์ดานิพจน์
Scott Dorman

พอใช้ฉันควรพูดแลมบ์ดาแล้ว ผู้รับมอบสิทธิ์จะทำงานใน. Net 2 แต่คุณจะต้องประกาศประเภททั่วไปที่ซ้อนกันอีกครั้งอย่างชัดเจน
Keith

5

ฉันคิดว่าไม่มีตัวพิมพ์ คุณสามารถกำหนดประเภทผู้รับมอบสิทธิ์เฉพาะแทนประเภททั่วไปใน GenericClass เช่น

public delegate GenericHandler EventHandler<EventData>

นี่จะทำให้สั้นลง แต่สิ่งที่เกี่ยวกับคำแนะนำต่อไปนี้:

ใช้ Visual Studio วิธีนี้เมื่อคุณพิมพ์

gcInt.MyEvent += 

มันได้เตรียมลายเซ็นตัวจัดการเหตุการณ์ที่สมบูรณ์จาก Intellisense แล้ว กด TAB และไปที่นั่น ยอมรับชื่อตัวจัดการที่สร้างขึ้นหรือเปลี่ยนแล้วกด TAB อีกครั้งเพื่อสร้างตัวจัดการต้นขั้วอัตโนมัติ


2
ใช่นั่นคือสิ่งที่ฉันทำเพื่อสร้างตัวอย่าง แต่กลับมาดูอีกครั้งหลังจากความจริงยังคงสับสน
Matthew Scharley

ฉันรู้คุณหมายถึงอะไร. นั่นเป็นเหตุผลที่ฉันต้องการให้ลายเซ็นเหตุการณ์ของฉันสั้นหรือหลีกเลี่ยงคำแนะนำ FxCop เพื่อใช้ Generic EventHandler <T> แทนที่จะเป็นประเภทตัวแทนของฉันเอง แต่แล้วติดกับรุ่นสั้น ๆ ที่ Jon Skeet จัดให้ :)
OregonGhost

2
หากคุณได้รับ ReSharper มันจะบอกคุณว่าเวอร์ชันที่ยาวเกินไป (โดยการระบายสีเป็นสีเทา) และคุณสามารถใช้ "การแก้ไขด่วน" เพื่อกำจัดมันอีกครั้ง
Roger Lipscombe

5

ทั้ง C ++ และ C # ขาดวิธีง่ายๆในการสร้างรูปแบบใหม่ซึ่งมีความหมายเหมือนกันกับประเภทที่มีอยู่ ฉันพบว่า 'typedefs' มีความสำคัญอย่างยิ่งต่อการเขียนโปรแกรมแบบปลอดภัยและความอัปยศที่แท้จริง c # ไม่ได้มีในตัว ความแตกต่างระหว่างvoid f(string connectionID, string username)ถึงvoid f(ConID connectionID, UserName username)ชัดเจน ...

(คุณสามารถบรรลุสิ่งที่คล้ายกันใน C ++ พร้อมกับเพิ่ม BOOST_STRONG_TYPEDEF)

อาจเป็นการดึงดูดให้ใช้มรดก แต่มีข้อ จำกัด ที่สำคัญบางประการ:

  • มันจะไม่ทำงานสำหรับประเภทดั้งเดิม
  • ประเภทที่ได้รับยังคงสามารถนำไปใช้กับประเภทดั้งเดิมได้เช่นเราสามารถส่งไปยังฟังก์ชันที่รับประเภทดั้งเดิมของเราได้
  • เราไม่สามารถสืบทอดมาจากคลาสที่ปิดผนึก (และเช่นคลาส. NET จำนวนมากจะถูกปิดผนึก)

วิธีเดียวที่จะบรรลุสิ่งที่คล้ายกันใน C # คือการเขียนประเภทของเราในคลาสใหม่:

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

ในขณะนี้จะใช้งานได้มันเป็น verbose มากเพียง typedef นอกจากนี้เรามีปัญหาเกี่ยวกับการทำให้เป็นอนุกรม (เช่น Json) เนื่องจากเราต้องการทำให้เป็นอนุกรมของคลาสผ่านคุณสมบัติการเขียน

ด้านล่างนี้เป็นคลาสผู้ช่วยที่ใช้ "รูปแบบเทมเพลตที่เกิดซ้ำ" เพื่อทำให้สิ่งนี้ง่ายขึ้นมาก:

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 คลาสดังกล่าวจะกลายเป็น:

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

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

และนอกจากนี้SomeTypeTypeDefจะทำให้เป็นอนุกรมกับ Json ในลักษณะเดียวกับที่SomeTypeทำ

หวังว่านี่จะช่วยได้!


4

คุณสามารถใช้ไลบรารีโอเพ่นซอร์สและแพ็คเกจ NuGet ชื่อLikeTypeที่ฉันสร้างขึ้นซึ่งจะให้GenericClass<int>พฤติกรรมที่คุณต้องการ

รหัสจะมีลักษณะดังนี้:

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/หรือไม่ ฉันได้ลองเล่นแล้วไม่สามารถใช้การตั้งค่าคลาสที่ใช้งานได้
Jay Croghan

นั่นไม่ใช่เจตนาของLikeTypeห้องสมุดจริงๆ LikeTypeจุดประสงค์หลักของมันคือการช่วยให้กับPrims Obsessionดังนั้นจึงไม่ต้องการให้คุณผ่านรอบที่ถูกห่อหุ้มเหมือนกับที่มันเป็นแบบ wrapper ในขณะที่ถ้าผมทำAge : LikeType<int>แล้วถ้าฟังก์ชั่นของฉันต้องการAgeฉันต้องการเพื่อให้แน่ใจว่าผู้ที่โทรเข้าของฉันจะส่งผ่านไม่ใช่Age int
Matt Klein

ที่ถูกกล่าวว่าฉันคิดว่าฉันมีคำตอบสำหรับคำถามของคุณซึ่งฉันจะโพสต์ที่นั่น
Matt Klein

3

นี่คือรหัสสำหรับมันเพลิดเพลิน! ฉันหยิบมันขึ้นมาจาก dotNetReference พิมพ์คำสั่ง "using" ในบรรทัดเนมสเปซ 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

สำหรับคลาสที่ไม่มีการปิดผนึกเพียงแค่สืบทอดจากพวกเขา:

public class Vector : List<int> { }

แต่สำหรับคลาสที่ปิดผนึกเป็นไปได้ที่จะจำลองพฤติกรรม typedef กับคลาสฐานดังกล่าว:

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

ทางเลือกที่ดีที่สุดในการtypedefที่ฉันได้พบใน C using# ตัวอย่างเช่นฉันสามารถควบคุมความแม่นยำในการลอยผ่านธงคอมไพเลอร์ด้วยรหัสนี้:

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

แต่น่าเสียดายที่มันต้องการให้คุณวางนี้ที่ด้านบนของทุกไฟล์real_tที่คุณใช้ ขณะนี้ยังไม่มีวิธีประกาศประเภทเนมสเปซส่วนกลางใน C #

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.