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

Есть ли эквивалент typedef в С# или каким-то образом получить какое-то подобное поведение? Я сделал какой-то поисковик, но везде, где я выгляжу, кажется отрицательным. В настоящее время у меня есть ситуация, аналогичная следующей:

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 вместо необходимости переопределять его для получения аналогичного поведения.

Ответ 1

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

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

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

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

gcInt.MyEvent += gcInt_MyEvent;

:)

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

gcInt.MyEvent += LowestCommonHander;

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

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

Ответ 5

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

public delegate GenericHandler EventHandler<EventData>

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

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

gcInt.MyEvent += 

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

(Вы можете добиться чего-то подобного в C++ с повышением в BOOST_STRONG_TYPEDEF)

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

  • это не будет работать для примитивных типов
  • производный тип все еще может быть приведен к исходному типу, т.е. мы можем отправить его функции, получающей наш исходный тип.
  • мы не можем наследовать от запечатанных классов (т.е. многие классы .NET запечатаны)

Единственный способ добиться подобного в С# - это создать наш тип в новом классе:

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

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

Надеюсь это поможет !

Ответ 9

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

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

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