Эквивалент 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
. В настоящее время нет способа объявить глобальный тип пространства имен в С#.