Tương đương typedef trong C #


326

Có một typedef tương đương trong C #, hoặc cách nào đó để có được một số loại hành vi tương tự? Tôi đã thực hiện một số googling, nhưng ở mọi nơi tôi trông có vẻ là tiêu cực. Hiện tại tôi có một tình huống tương tự như sau:

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

Bây giờ, không cần một nhà khoa học tên lửa phát hiện ra rằng điều này có thể nhanh chóng dẫn đến việc đánh máy rất nhiều (xin lỗi vì sự chơi chữ khủng khiếp) khi cố gắng thực hiện một xử lý cho sự kiện đó. Nó sẽ trở thành một cái gì đó như thế này:

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

Ngoại trừ, trong trường hợp của tôi, tôi đã sử dụng một loại phức tạp, không chỉ là một int. Thật tuyệt nếu có thể đơn giản hóa điều này một chút ...

Chỉnh sửa: tức là. có lẽ đã đánh máy EventHandler thay vì cần xác định lại nó để có hành vi tương tự.

Câu trả lời:


341

Không, không có tương đương thực sự của typedef. Bạn có thể sử dụng các chỉ thị 'bằng cách sử dụng' trong một tệp, ví dụ:

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

nhưng điều đó sẽ chỉ ảnh hưởng đến tập tin nguồn đó. Trong C và C ++, kinh nghiệm của tôi typedeflà thường được sử dụng trong các tệp .h được bao gồm rộng rãi - vì vậy một tệp typedefcó thể được sử dụng trong toàn bộ dự án. Khả năng đó không tồn tại trong C #, vì không có #includechức năng nào trong C # cho phép bạn đưa các usingchỉ thị từ một tệp vào tệp khác.

May mắn thay, ví dụ bạn đưa ra không có một sửa chữa - chuyển đổi nhóm phương pháp ngầm. Bạn có thể thay đổi dòng đăng ký sự kiện của mình thành:

gcInt.MyEvent += gcInt_MyEvent;

:)


11
Tôi luôn quên rằng bạn có thể làm điều này. Có lẽ bởi vì Visual Studio gợi ý phiên bản dài dòng hơn. Nhưng tôi vẫn ổn khi nhấn TAB hai lần thay vì nhập tên trình xử lý;)
OregonGhost

11
Theo kinh nghiệm của tôi (vốn khan hiếm), bạn phải chỉ định tên loại đủ điều kiện, ví dụ: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; Có đúng không? Mặt khác, nó dường như không xem xét các usingđịnh nghĩa ở trên nó.
tunnuz

3
Tôi không thể chuyển đổi typedef uint8 myuuid[16];thông qua "sử dụng" chỉ thị. using myuuid = Byte[16];không biên dịch. usingcó thể được sử dụng chỉ để tạo bí danh loại . typedefdường như linh hoạt hơn nhiều, vì nó có thể tạo bí danh cho toàn bộ khai báo (bao gồm cả kích thước mảng). Có sự thay thế nào trong trường hợp này không?
natenho

2
@natenho: Không hẳn. Gần nhất bạn có thể đến sẽ có một cấu trúc với bộ đệm kích thước cố định, có lẽ.
Jon Skeet

1
@tunnuz Trừ khi bạn chỉ định nó trong một không gian tên
John Smith

38

Jon thực sự đã đưa ra một giải pháp tốt đẹp, tôi không biết bạn có thể làm điều đó!

Đôi khi những gì tôi dùng đến là kế thừa từ lớp và tạo ra các hàm tạo của nó. Ví dụ

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

Không phải là giải pháp tốt nhất (trừ khi lắp ráp của bạn được người khác sử dụng), nhưng nó hoạt động.


41
Chắc chắn là một phương pháp tốt, nhưng hãy nhớ rằng những loại (gây phiền nhiễu) đó tồn tại và nó sẽ không hoạt động ở đó. Tôi thực sự muốn C # sẽ giới thiệu typedefs rồi. Đó là một nhu cầu tuyệt vọng (đặc biệt đối với các lập trình viên C ++).
MasterMastic

1
Tôi đã tạo một dự án cho tình huống này được gọi là LikeType, nó bao bọc kiểu cơ bản hơn là kế thừa từ nó. Nó cũng sẽ ngầm chuyển đổi ĐẾN loại cơ bản, vì vậy bạn có thể sử dụng một cái gì đó giống như public class FooList : LikeType<IReadOnlyList<Foo>> { ... }và sau đó sử dụng nó bất cứ nơi nào bạn mong chờ một IReadOnlyList<Foo>. Câu trả lời của tôi dưới đây cho thấy chi tiết hơn.
Matt Klein

3
Nó cũng sẽ không suy ra kiểu Foonếu được truyền cho ví dụ như phương thức mẫu chấp nhận List<T>. Với typedef thích hợp nó sẽ có thể.
Aleksei Petrenko

18

Nếu bạn biết bạn đang làm gì, bạn có thể định nghĩa một lớp với các toán tử ẩn để chuyển đổi giữa lớp bí danh và lớp thực tế.

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

Tôi thực sự không tán thành điều này và chưa bao giờ sử dụng một cái gì đó như thế này, nhưng điều này có thể có thể làm việc cho một số trường hợp cụ thể.


Cảm ơn @palswim, tôi đã đến đây để tìm kiếm một cái gì đó như "Định danh chuỗi typedef;" vì vậy đề nghị của bạn có thể chỉ là những gì tôi cần.
yoyo

6

C # hỗ trợ một số hiệp phương sai được kế thừa cho các đại biểu sự kiện, vì vậy một phương pháp như sau:

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

Có thể được sử dụng để đăng ký sự kiện của bạn, không yêu cầu diễn viên rõ ràng

gcInt.MyEvent += LowestCommonHander;

Bạn thậm chí có thể sử dụng cú pháp lambda và tất cả các intellisense sẽ được thực hiện cho bạn:

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

Tôi thực sự cần phải đi xung quanh để có một cái nhìn tốt về Linq ... mặc dù vậy, tôi đang xây dựng phiên bản 2.0 vào thời điểm đó (trong VS 2008)
Matthew Scharley

Ồ, ngoài ra, tôi có thể đăng ký tốt, nhưng sau đó để có được các đối số sự kiện tôi cần một diễn viên rõ ràng, và tốt nhất là gõ mã kiểm tra, chỉ để ở bên an toàn.
Matthew Scharley

9
Cú pháp là chính xác, nhưng tôi sẽ không nói đó là "Cú pháp Linq"; đúng hơn nó là một biểu hiện lambda. Lambdas là một tính năng hỗ trợ giúp Linq hoạt động, nhưng hoàn toàn độc lập với nó. Về cơ bản, bất cứ nơi nào bạn có thể sử dụng một đại biểu, bạn có thể sử dụng biểu thức lambda.
Scott Dorman

Điểm công bằng, tôi nên nói lambda. Một đại biểu sẽ làm việc trong .Net 2, nhưng bạn cần khai báo lại loại chung chung lồng nhau.
Keith

5

Tôi nghĩ rằng không có typedef. Bạn chỉ có thể xác định loại đại biểu cụ thể thay vì loại chung trong GenericClass, nghĩa là

public delegate GenericHandler EventHandler<EventData>

Điều này sẽ làm cho nó ngắn hơn. Nhưng những gì về gợi ý sau:

Sử dụng Visual Studio. Bằng cách này, khi bạn gõ

gcInt.MyEvent += 

nó đã cung cấp chữ ký xử lý sự kiện hoàn chỉnh từ Intellisense. Nhấn TAB và nó ở đó. Chấp nhận tên trình xử lý đã tạo hoặc thay đổi nó, sau đó nhấn TAB lần nữa để tự động tạo sơ đồ xử lý.


2
Phải, đó là những gì tôi đã làm để tạo ra ví dụ. Nhưng quay lại để xem xét nó một lần nữa SAU thực tế vẫn có thể gây nhầm lẫn.
Matthew Scharley

Tôi hiểu bạn muốn nói gì. Đó là lý do tại sao tôi muốn giữ chữ ký sự kiện của mình ngắn hoặc tránh xa đề xuất FxCop để sử dụng Generic EventHandler <T> thay vì loại đại biểu của riêng tôi. Nhưng sau đó, hãy gắn bó với phiên bản tay ngắn do Jon Skeet cung cấp :)
OregonGhost

2
Nếu bạn đã có ReSharper, nó sẽ cho bạn biết rằng phiên bản dài là quá mức cần thiết (bằng cách tô màu nó bằng màu xám) và bạn có thể sử dụng "cách khắc phục nhanh" để loại bỏ nó một lần nữa.
Roger Lipscombe

5

Cả C ++ và C # đều thiếu những cách dễ dàng để tạo ra một loại mới giống hệt về mặt ngữ nghĩa với một loại hiện tại. Tôi thấy những 'typedefs' như vậy hoàn toàn cần thiết cho lập trình an toàn kiểu và thật xấu hổ c # không có chúng tích hợp sẵn. Sự khác biệt giữa void f(string connectionID, string username)để void f(ConID connectionID, UserName username)là rõ ràng ...

(Bạn có thể đạt được thứ gì đó tương tự trong C ++ với boost trong BOOST_STRONG_TYPEDEF)

Nó có thể hấp dẫn để sử dụng thừa kế nhưng điều đó có một số hạn chế lớn:

  • nó sẽ không hoạt động cho các loại nguyên thủy
  • loại dẫn xuất vẫn có thể được chuyển sang loại ban đầu, tức là chúng ta có thể gửi nó đến một hàm nhận loại ban đầu của chúng ta, điều này đánh bại toàn bộ mục đích
  • chúng ta không thể xuất phát từ các lớp niêm phong (và tức là nhiều lớp .NET được niêm phong)

Cách duy nhất để đạt được điều tương tự trong C # là kết hợp kiểu của chúng tôi trong một lớp mới:

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

Trong khi điều này sẽ làm việc, nó rất dài dòng cho một typedef. Ngoài ra, chúng tôi có một vấn đề với việc tuần tự hóa (tức là với Json) vì chúng tôi muốn tuần tự hóa lớp thông qua thuộc tính Hợp thành của nó.

Dưới đây là lớp trình trợ giúp sử dụng "Mẫu mẫu định kỳ tò mò" để làm cho việc này đơn giản hơn nhiều:

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

}

Với Trình soạn thảo, lớp trên trở nên đơn giản:

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

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

Và ngoài ra, SomeTypeTypeDefnó sẽ tuần tự hóa thành Json theo cách tương tự SomeType.

Hi vọng điêu nay co ich !


4

Bạn có thể sử dụng thư viện mã nguồn mở và gói NuGet có tên LikeType mà tôi đã tạo sẽ cung cấp cho bạn GenericClass<int>hành vi mà bạn đang tìm kiếm.

Mã sẽ trông như sau:

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 có hoạt động với những thứ phức tạp như stackoverflow.com/questions/50404586/ không? Tôi đã thử chơi với nó và không thể có được một thiết lập lớp hoạt động.
Jay Cro Afghanistan

Đó không thực sự là mục đích của LikeTypethư viện. LikeTypeMục đích chính của nó là giúp đỡ với Nỗi ám ảnh nguyên thủy , và như vậy, nó không muốn bạn có thể vượt qua loại được bọc như nó là loại bao bọc. Như trong, nếu tôi thực hiện Age : LikeType<int>thì nếu chức năng của tôi cần một Age, tôi muốn đảm bảo người gọi của tôi sẽ vượt qua Agechứ không phải một int.
Matt Klein

Điều đó đang được nói, tôi nghĩ rằng tôi có câu trả lời cho câu hỏi của bạn, mà tôi sẽ đăng ở đó.
Matt Klein

3

Đây là mã cho nó, thích !, Tôi đã chọn lên từ dotNetReference gõ "sử dụng" tuyên bố bên trong đường namespace 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

Đối với các lớp không niêm phong chỉ đơn giản là kế thừa từ chúng:

public class Vector : List<int> { }

Nhưng đối với các lớp niêm phong, có thể mô phỏng hành vi typedef với lớp cơ sở đó:

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

Sự thay thế tốt nhất typedefmà tôi đã tìm thấy trong C # là using. Ví dụ: tôi có thể kiểm soát độ chính xác float thông qua các cờ biên dịch với mã này:

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

Thật không may, nó yêu cầu bạn đặt nó ở đầu mỗi tệp bạn sử dụng real_t. Hiện tại không có cách nào để khai báo một loại không gian tên toàn cầu trong C #.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.