Эквивалент 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#.