Еквівалент typedef в C #


326

Чи є еквівалент typedef в C #, або якимось чином отримати подібну поведінку? Я зробив кілька гуглів, але скрізь, де я виглядаю, це негативно. В даний час у мене ситуація схожа на наступну:

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 CustomerList = System.Collections.Generic.List<Customer>;

але це вплине лише на цей вихідний файл. У мовах C і C ++ мій досвід полягає в тому, що typedefзазвичай використовується у typedef. Ця здатність не існує в C #, оскільки в C # немає жодної #includeфункціональності, яка дозволила б вам включати 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: Не дуже. Найближчим, до якого ви могли б підійти, було б мати структуру з буфером фіксованого розміру, ймовірно.
Джон Скіт

1
@tunnuz Якщо ви не вказали його всередині простору імен
Джон Сміт

38

Джон дійсно дав гарне рішення, я не знав, що ти можеш це зробити!

Часом, до чого я вдався, успадковував клас і створював його конструктори. Напр

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

Не найкраще рішення (якщо ваша асамблея не використовується іншими людьми), але вона працює.


41
Безумовно, хороший метод, але майте на увазі, що ті (дратівливі) герметичні типи існують, і він не працюватиме там. Я дуже хочу, щоб C # вже вводив typedefs. Це відчайдушна потреба (особливо для програмістів на C ++).
MasterMastic

1
Я створив проект для цієї ситуації під назвою LikeType, який обгортає базовий тип, а не успадковує його. Він також неявно перетворить TO в базовий тип, так що ви можете використовувати щось на зразок, public class FooList : LikeType<IReadOnlyList<Foo>> { ... }а потім використовувати його в будь-якому місці, на яке ви очікували б IReadOnlyList<Foo>. Моя відповідь нижче показує більше деталей.
Метт Кляйн

3
Він також не визначає тип, Fooякщо він передається, наприклад, методу шаблону, який приймає List<T>. При правильному typedef це було б можливо.
Олексій Петренко

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

О, також, я можу підписатися прекрасно, але тоді для отримання аргументів події мені потрібен чіткий склад, і бажано ввести перевіряючий код, просто щоб бути захищеним.
Меттью Шарлі

9
Синтаксис правильний, але я б не сказав, що це "синтаксис Linq"; швидше це лямбда-вираз. Лямбди - це допоміжна функція, яка змушує Linq працювати, але повністю незалежна від нього. По суті, де б ви не могли використовувати делегата, ви можете використовувати лямбда-вираз.
Скотт Дорман

Справедливий момент, я повинен був сказати лямбда. Делегат працюватиме в .Net 2, але вам потрібно буде чітко оголосити вкладений загальний тип знову.
Кіт

5

Я думаю, що немає typedef. Ви можете визначити лише певний тип делегата замість загального в GenericClass, тобто

public delegate GenericHandler EventHandler<EventData>

Це зробило б її коротшою. А як щодо наступної пропозиції:

Використовуйте Visual Studio. Таким чином, коли ви вводили текст

gcInt.MyEvent += 

він уже забезпечує повний підпис обробника подій від Intellisense. Натисніть TAB і там. Прийміть створене ім'я обробника або змініть його, а потім знову натисніть TAB, щоб автоматично генерувати заглушку обробника.


2
Так, саме це я зробив для створення прикладу. Але повернутися до того, щоб знову подивитися на це ПІСЛЯ факту все ще може бути заплутаним.
Меттью Шарлі

Я знаю, що ти маєш на увазі. Ось чому мені подобається, щоб мої підписи були короткими, або відходили від рекомендації FxCop використовувати Generic EventHandler <T> замість власного типу делегата. Але тоді дотримуйтесь короткошерстної версії, наданої Джоном
Скітом

2
Якщо у вас є ReSharper, він скаже вам, що довга версія є надмірною (забарвивши її в сірий колір), і ви можете скористатися "швидким виправленням", щоб знову її позбутися.
Роджер Ліпскомб

5

І в C ++, і в C # відсутні легкі способи створення нового типу, що семантично ідентичний існуючому типу. Мені здається, що такі "typedefs" абсолютно необхідні для безпечного програмування, і справжній сором c # не має їх вбудованих. Різниця між void f(string connectionID, string username)to 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();
}

Хоча це буде працювати, це дуже багатослівно лише для typedef. Крім того, у нас є проблеми з серіалізацією (тобто до Json), оскільки ми хочемо серіалізувати клас через його властивість Composed.

Нижче представлений клас помічників, який використовує "Цікаво повторюваний шаблон шаблону", щоб зробити це набагато простіше:

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

}

З композитором вищевказаний клас стає просто:

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

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

І крім того SomeTypeTypeDef заповіт буде серіалізуватися на Джсона так само, як це 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));
    }
}

ХочеТип працює для чогось такого складного, як stackoverflow.com/questions/50404586/… ? Я спробував грати з ним і не можу отримати налаштування класу, яке працює.
Джей Кроган

Це насправді не намір LikeTypeбібліотеки. LikeTypeОсновна мета - допомогти з примітивною одержимістю , і тому вона не хоче, щоб ви могли пройти навколо загорнутого типу, як це був тип обгортки. Як і у випадку, якщо я зроблю Age : LikeType<int>тоді, якщо моя функція потребує Age, я хочу переконатися, що мої абоненти передають а Age, а не антенну int.
Метт Кляйн

Зважаючи на це, я думаю, що у мене є відповідь на ваше запитання, яке я опублікую там.
Метт Кляйн

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 #.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.