Эквивалент typedef в C#

есть ли эквивалент 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. Было бы неплохо, если бы это можно было немного упростить...

Edit: ie. возможно, typedefing EventHandler вместо необходимости переопределять его, чтобы получить аналогичное поведение.

9 ответов


нет, нет истинного эквивалента typedef. Вы можете использовать директивы "using" в одном файле, например

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

но это повлияет только на этот исходный файл. В C и C++, мой опыт заключается в том, что typedef обычно используется внутри .H файлы, которые включены широко-так один typedef может использоваться в течение всего проекта. Эта способность не существует в C#, потому что нет #include функциональность в C#, которая позволит вам включить using директивы из одного файла в другой.

к счастью, пример вы даете тут есть fix-неявное преобразование группы методов. Вы можете изменить строку подписки на событие просто:

gcInt.MyEvent += gcInt_MyEvent;

:)


Джон действительно дал хорошее решение, я не знал, что вы можете это сделать!

иногда я прибегал к наследованию от класса и созданию его конструкторов. Е. Г.

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

Не лучшее решение (если ваша сборка не используется другими людьми), но она работает.


Если вы знаете, что делаете, вы можете определить класс с неявными операторами для преобразования между классом псевдонима и фактическим классом.

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

Я на самом деле не поддерживаю это и никогда не использовал что-то подобное, но это, вероятно, может работать для некоторых конкретных обстоятельств.


C# поддерживает некоторую унаследованную ковариацию для делегатов событий, поэтому такой метод:

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

может использоваться для подписки на ваше событие, не требуется явного приведения

gcInt.MyEvent += LowestCommonHander;

вы даже можете использовать синтаксис лямбда и intellisense все будет сделано для вас:

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

Я думаю, что нет typedef. Вы можете определить только конкретный тип делегата вместо универсального в GenericClass, т. е.

public delegate GenericHandler EventHandler<EventData>

это сделает его короче. Но как насчет следующего предложения:

Используйте Visual Studio. Таким образом, когда вы набрали

gcInt.MyEvent += 

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


Вы можете использовать библиотеку с открытым исходным кодом и пакет 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));
    }
}

вот код для него, наслаждайтесь!, Я взял это из 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"};
        }
    }
}

я нахожу typedefs абсолютно необходимым для безопасного программирования, и его настоящий позор c# не имеет их встроенных. Разница между void f(string connectionID, string username) до void f(ConID connectionID, UserName username) - это очевидно ...

может быть заманчиво использовать наследование, но это имеет некоторые основные ограничения:

  • он не будет работать для примитивных типов
  • производный тип все еще может быть приведен к исходному типу, т. е. мы можем отправить его в функцию, получающую наш исходный тип, это побеждает вся цель
  • мы не можем наследовать от запечатанных классов (и т. е. многие классы .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) { var o = obj as SomeTypeTypeDef; return o is null ? false : Composed.Equals(o.Composed); }
  public bool Equals(SomeTypeTypeDefo) => object.Equals(this, o);

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

хотя это будет работать, это очень многословно только для 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) { var o = obj as TDerived; return o is null ? false : 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 сериализуется в Json таким же образом, что SomeType делает.

внимание, если мы реализуем == и != on SomeTypeTypeDef компилятор предупредит, что Equals отсутствует, хотя он правильно унаследован, мы можем безопасно использовать:

#pragma warning disable CS0660, CS0661

отключить эти предупреждающий.

необходимость прокси-методов может быть хлопот, но также является благословением в маскировке, будучи новым типом, мы часто хотим только FW выбранные методы и добавить новые в наш "typedef"

надеюсь, что это помогает !


лучшая альтернатива typedef что я нашел в C#using. Например, я могу управлять точностью float через флаги компилятора с помощью этого кода:

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

к сожалению, это требует, чтобы вы разместили это в верхней части файл где вы используете real_t. В настоящее время нет способа объявить глобальный тип пространства имен в C#.