等效于 C# 中的 typedef
本文关键字:中的 typedef | 更新日期: 2023-09-27 17:47:23
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。如果可以稍微简化一下就好了......
编辑:即也许对事件处理程序进行类型定义,而不是需要重新定义它以获得类似的行为。
不,没有真正的等价物 typedef。您可以在一个文件中使用"using"指令,例如
using CustomerList = System.Collections.Generic.List<Customer>;
但这只会影响该源文件。在 C 和 C++ 中,我的经验是typedef
通常在广泛包含的 .h 文件中使用 - 因此单个typedef
可以用于整个项目。C# 中不存在此功能,因为 C# 中没有#include
功能允许您将一个文件中的 using
指令包含在另一个文件中。
幸运的是,您给出的示例确实有一个修复 - 隐式方法组转换。您可以将活动订阅行更改为仅:
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++ 和 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)方面存在问题,因为我们希望通过其 Composite 属性序列化类。
下面是一个帮助程序类,它使用"奇怪的重复模板模式"来简化此操作:
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
将以与SomeType
相同的方式序列化为 Json。
希望这有帮助!
使用 C# 10 您现在可以
global using Bar = Foo
它的工作方式类似于项目中的 typedef。
我还没有深入测试它,所以可能会有怪癖。
我像这样使用它
global using DateTime = DontUseDateTime
其中DontUseDateTime是一个标记为过时的结构,以强制人们使用NodaTime。
我认为没有typedef。您只能定义特定的委托类型,而不是 GenericClass 中的泛型委托类型,即
public delegate GenericHandler EventHandler<EventData>
这将使它更短。但是以下建议呢:
使用Visual Studio。这样,当您键入
gcInt.MyEvent +=
它已提供来自智能感知的完整事件处理程序签名。按 TAB 键,它就在那里。接受生成的处理程序名称或更改它,然后再次按 Tab 以自动生成处理程序存根。
C# 支持事件委托的一些继承协方差,因此方法如下:
void LowestCommonHander( object sender, EventArgs e ) { ... }
可用于订阅您的活动,无需显式投射
gcInt.MyEvent += LowestCommonHander;
您甚至可以使用 lambda 语法,智能感知将为您完成:
gcInt.MyEvent += (sender, e) =>
{
e. //you'll get correct intellisense here
};
可以使用我创建的名为 LikeType 的开源库和 NuGet 包,它将为您提供所需的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中挑选了它在命名空间行 106 内键入 "using" 语句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"};
}
}
}
做的
using System.Collections.Generic;
global using CustomerList = List<Customer>;
<</div>
div class="answers"> 对于非密封类,只需从它们继承:
public class Vector : List<int> { }
但是对于密封类,可以使用这样的基类模拟 typedef 行为:
public abstract class Typedef<T, TDerived> where TDerived : Typedef<T, TDerived>, new()
{
private T _value;
public static implicit operator T(Typedef<T, TDerived> t)
{
return t == null ? default : t._value;
}
public static implicit operator Typedef<T, TDerived>(T t)
{
return t == null ? default : new TDerived { _value = t };
}
}
// Usage examples
class CountryCode : Typedef<string, CountryCode> { }
class CurrencyCode : Typedef<string, CurrencyCode> { }
class Quantity : Typedef<int, Quantity> { }
void Main()
{
var canadaCode = (CountryCode)"CA";
var canadaCurrency = (CurrencyCode)"CAD";
CountryCode cc = canadaCurrency; // Compilation error
Concole.WriteLine(canadaCode == "CA"); // true
Concole.WriteLine(canadaCurrency); // CAD
var qty = (Quantity)123;
Concole.WriteLine(qty); // 123
}
C# 中找到的typedef
的最佳替代方案是 using
。例如,我可以使用以下代码通过编译器标志控制浮点精度:
#if REAL_T_IS_DOUBLE
using real_t = System.Double;
#else
using real_t = System.Single;
#endif
不幸的是,它要求您将其放在使用 real_t
的每个文件的顶部。目前无法在 C# 中声明全局命名空间类型。
自从引入 C# 10.0 以来,我们现在有了 global using
指令。
global using CustomerList = System.Collections.Generic.List<Customer>;
这将CustomerList
作为全局范围内List<Customer>
的别名引入(贯穿整个项目及其所有引用)。
虽然我希望能够限制它的范围(例如"内部使用"),但这实际上在实现 C# 中的 typedef 变体方面做得非常出色。