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

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

290 голосов
/ 02 октября 2008

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

Ответы [ 9 ]

312 голосов
/ 02 октября 2008

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

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

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

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

gcInt.MyEvent += gcInt_MyEvent;

:)

35 голосов
/ 02 октября 2008

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

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

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

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

18 голосов
/ 22 февраля 2012

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

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

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

6 голосов
/ 02 октября 2008

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

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

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

gcInt.MyEvent += LowestCommonHander;

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

gcInt.MyEvent += (sender, e) =>
{
    e. //you'll get correct intellisense here
};
5 голосов
/ 02 октября 2008

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

public delegate GenericHandler EventHandler<EventData>

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

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

gcInt.MyEvent += 

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

3 голосов
/ 13 марта 2016

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

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

Как в 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();
}

Хотя это сработает, это очень многословно только для 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);
    }
  }

}

С 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"

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

0 голосов
/ 30 марта 2018

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

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

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

...