【问题标题】:Equivalent of typedef in C#C# 中 typedef 的等价物
【发布时间】:2010-09-14 18:12:21
【问题描述】:

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 进行类型定义,而不是需要重新定义它以获得类似的行为。

【问题讨论】:

    标签: c# typedef


    【解决方案1】:

    不,没有真正的 typedef 等价物。您可以在一个文件中使用“使用”指令,例如

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

    但这只会影响该源文件。在 C 和 C++ 中,我的经验是 typedef 通常在广泛包含的 .h 文件中使用 - 因此单个 typedef 可以在整个项目中使用。这种能力在 C# 中不存在,因为 C# 中没有 #include 功能可以让您在另一个文件中包含 using 指令。

    幸运的是,您给出的示例确实有一个修复 - 隐式方法组转换。您可以将您的活动订阅行更改为:

    gcInt.MyEvent += gcInt_MyEvent;
    

    :)

    【讨论】:

    • 我总是忘记你可以做到这一点。也许是因为 Visual Studio 建议使用更详细的版本。但是我可以按两次 TAB 而不是输入处理程序名称;)
    • 根据我的经验(很少见),您必须指定完全限定的类型名称,例如:using MyClassDictionary = System.Collections.Generic.Dictionary&lt;System.String, MyNamespace.MyClass&gt;; 是否正确?否则它似乎没有考虑上面的using 定义。
    • 我无法通过“使用”指令转换typedef uint8 myuuid[16];using myuuid = Byte[16]; 无法编译。 using 只能用于创建 type 别名。 typedef 似乎更加灵活,因为它可以为整个声明(包括数组大小)创建别名。在这种情况下有其他选择吗?
    • @natenho:不是真的。最接近的可能是拥有一个具有固定大小缓冲区的结构。
    • @tunnuz 除非你在命名空间中指定它
    【解决方案2】:

    我认为没有 typedef。您只能在 GenericClass 中定义一个特定的委托类型,而不是通用的,即

    public delegate GenericHandler EventHandler<EventData>
    

    这会使它更短。但是下面的建议呢:

    使用 Visual Studio。这样,当您输入时

    gcInt.MyEvent += 
    

    它已经提供了来自 Intellisense 的完整事件处理程序签名。按TAB,它就在那里。接受生成的处理程序名称或更改它,然后再次按 TAB 以自动生成处理程序存根。

    【讨论】:

    • 是的,这就是我为生成示例所做的。但是在事实发生之后再回来看它仍然会令人困惑。
    • 我知道你的意思。这就是为什么我喜欢让我的事件签名保持简短,或者远离 FxCop 的建议,使用 Generic EventHandler 而不是我自己的委托类型。但是,坚持使用 Jon Skeet 提供的速记版本 :)
    • 如果你有 ReSharper,它会告诉你长版本是多余的(通过将它涂成灰色),你可以使用“快速修复”再次摆脱它。
    【解决方案3】:

    Jon 真的给出了一个很好的解决方案,我不知道你能做到!

    有时我采用的是从类继承并创建它的构造函数。例如

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

    不是最好的解决方案(除非您的程序集被其他人使用),但它确实有效。

    【讨论】:

    • 绝对是一个好方法,但请记住,那些(烦人的)密封类型存在,它不会在那里工作。我真的希望 C# 已经引入了 typedef。这是一种迫切需要(尤其是对于 C++ 程序员)。
    • 我为这种情况创建了一个名为 LikeType 的项目,它封装了底层类型而不是从它继承。它还将隐式转换 TO 基础类型,因此您可以使用类似public class FooList : LikeType&lt;IReadOnlyList&lt;Foo&gt;&gt; { ... } 的内容,然后在您期望IReadOnlyList&lt;Foo&gt; 的任何地方使用它。 My answer 下面显示了更多细节。
    • 它也不会推断类型 Foo 如果传递给例如接受List&lt;T&gt; 的模板方法。使用适当的 typedef 是可能的。
    【解决方案4】:

    C# 支持事件委托的一些继承协变,所以像这样的方法:

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

    可用于订阅您的活动,无需显式转换

    gcInt.MyEvent += LowestCommonHander;
    

    您甚至可以使用 lambda 语法,智能感知将全部为您完成:

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

    【讨论】:

    • 我真的需要好好看看 Linq ......不过,为了记录,我当时正在构建 2.0(虽然在 VS 2008 中)
    • 哦,我也可以订阅,但为了获得事件参数,我需要显式转换,最好是类型检查代码,以确保安全。
    • 语法是正确的,但我不会说它是“Linq 语法”;相反,它是一个 lambda 表达式。 Lambda 是使 Linq 工作的支持功能,但完全独立于它。本质上,在任何可以使用委托的地方,都可以使用 lambda 表达式。
    • 公平点,我应该说 lambda。委托可以在 .Net 2 中工作,但您需要再次显式声明嵌套的泛型类型。
    【解决方案5】:

    如果你知道你在做什么,你可以定义一个带有隐式操作符的类来在别名类和实际类之间进行转换。

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

    我实际上并不认可这一点,也从未使用过类似的东西,但这可能适用于某些特定情况。

    【讨论】:

    • 谢谢@palswim,我来这里是为了寻找类似“typedef string Identifier;”的东西所以你的建议可能正是我所需要的。
    • 我在数据库系统中经常使用这种方法,因此虽然 CustomerId 和 OrderId 是通常为 Int32 的 32 位整数值,但它们在程序中表示为不同的类型,所以没有- 一个人会不小心GetCustomerById( orderId )。也就是说,使用struct,而不是class 以避免不必要的堆分配。
    【解决方案6】:

    您可以使用我创建的名为 LikeType 的开源库和 NuGet 包,它会为您提供您正在寻找的 GenericClass&lt;int&gt; 行为。

    代码如下:

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

    【讨论】:

    • LikeType 是否适用于像 stackoverflow.com/questions/50404586/… 这样复杂的东西?我已经尝试过使用它,但无法获得有效的课程设置。
    • 这并不是LikeType 库的真正意图。 LikeType 的主要目的是帮助 Primitive Obsession,因此,它不希望您能够像传递封装类型那样传递封装类型。例如,如果我创建Age : LikeType&lt;int&gt;,那么如果我的函数需要Age,我想确保我的调用者传递的是Age,而不是int
    • 话虽如此,我想我已经回答了你的问题,我会在那里发布。
    【解决方案7】:

    这是它的代码,享受吧!我是从 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"};
            }
        }
    }
    

    【讨论】:

      【解决方案8】:

      我在 C# 中找到的 typedef 的最佳替代品是 using。例如,我可以使用以下代码通过编译器标志控制浮点精度:

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

      不幸的是,它要求您将其放在使用real_t每个文件 的顶部。目前无法在 C# 中声明全局命名空间类型。

      【讨论】:

        【解决方案9】:

        C++ 和 C# 都缺少创建 new 类型的简单方法,该类型在语义上与现有类型相同。我发现这样的'typedefs'对于类型安全的编程来说是完全必要的,而且真的很遗憾 c# 没有内置它们。 void f(string connectionID, string username)void f(ConID connectionID, UserName username) 的区别很明显……

        (您可以在 C++ 中通过 BOOST_STRONG_TYPEDEF 中的 boost 实现类似的功能)

        使用继承可能很诱人,但这有一些主要限制:

        • 它不适用于原始类型
        • 派生类型仍然可以转换为原始类型,即我们可以将其发送到接收原始类型的函数,这违背了整个目的
        • 我们不能从密封类派生(即许多 .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 属性对类进行序列化。

        下面是一个帮助类,它使用“Curiously Recurring Template Pattern”来简化它:

        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。

        希望这会有所帮助!

        【讨论】:

          【解决方案10】:

          对于非密封类,只需从它们继承:

          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
          }
          

          【讨论】:

            【解决方案11】:

            使用 C# 10,您现在可以做到

            global using Bar = Foo
            

            这就像项目中的 typedef。

            我没有深入测试过,所以可能有怪癖。

            我正在使用它

            global using DateTime = DontUseDateTime
            

            DontUseDateTime 是一个标记为 Obsolete 的结构,用于强制人们使用 NodaTime。

            【讨论】:

              猜你喜欢
              • 2018-08-21
              • 2011-04-02
              • 2021-10-15
              • 2013-10-20
              • 2011-04-10
              • 2021-12-02
              • 2016-12-24
              • 2013-02-10
              相关资源
              最近更新 更多