与C#中的typedef等效


326

在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进行typedef定义,而不必重新定义它以获得类似的行为。

Answers:


341

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

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

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

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

gcInt.MyEvent += gcInt_MyEvent;

:)


11
我总是忘记了你可以做到这一点。可能是因为Visual Studio建议使用更详细的版本。但我可以按两次TAB而不是键入处理程序名称就可以了;)
OregonGhost,

11
以我的经验(很少),您必须指定完全限定的类型名称,例如: using MyClassDictionary = System.Collections.Generic.Dictionary<System.String, MyNamespace.MyClass>; 是否正确?否则,似乎没有考虑using上面的定义。
tunnuz

3
我无法typedef uint8 myuuid[16];通过“使用”指令进行转换。using myuuid = Byte[16];无法编译。using只能用于创建类型别名。typedef似乎更灵活,因为它可以为整个声明(包括数组大小)创建别名。在这种情况下还有其他选择吗?
natenho 2014年

2
@natenho:不是。您可能最接近的可能是具有固定大小缓冲区的结构。
乔恩·斯基特

1
@tunnuz除非您在名称空间中指定它
John Smith,

38

乔恩确实提供了一个不错的解决方案,但我不知道您能做到这一点!

有时我求助于从类继承并创建其构造函数。例如

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

不是最好的解决方案(除非您的程序集被其他人使用),但是它可以工作。


41
绝对是个好方法,但是请记住,存在那些(讨厌的)密封类型,并且在那里不起作用。我真的希望C#已经引入typedef。这是一个迫切的需求(尤其是对于C ++程序员)。
2012年

1
我针对这种情况创建了一个名为LikeType的项目,该项目包装了基础类型而不是从其继承。它也将隐式转换TO的基本类型,所以你可以使用像public class FooList : LikeType<IReadOnlyList<Foo>> { ... },然后使用它的任何地方,你会期望IReadOnlyList<Foo>在下面的答案显示了更多细节。
马特·克莱恩

3
Foo如果传递给例如accepts的模板方法,它也不会推断类型List<T>。使用适当的typedef,这将是可能的。
Aleksei Petrenko

18

如果您知道自己在做什么,则可以使用隐式运算符定义一个类,以在别名类和实际类之间进行转换。

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字符串标识符”之类的东西。所以您的建议可能正是我所需要的。
yoyo

6

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中)
Matthew Scharley'Oct

哦,我也可以订阅,但是为了获得事件参数,我需要显式强制转换,最好是类型检查代码,以确保安全。
马修·沙利

9
语法是正确的,但我不会说它是“ Linq语法”。而是一个lambda表达式。Lambda是使Linq正常工作的支持功能,但完全独立于它。本质上,在任何可以使用委托的地方,都可以使用lambda表达式。
Scott Dorman

公平点,我应该说lambda。委托可以在.Net 2中使用,但是您需要再次显式声明嵌套的泛型类型。
基思

5

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

public delegate GenericHandler EventHandler<EventData>

这将使其更短。但是以下建议呢?

使用Visual Studio。这样,当您键入

gcInt.MyEvent += 

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


2
是的,这就是我生成示例的过程。但是,在事实发生后再回头看看仍然会造成混乱。
马修·沙利

我知道你的意思。这就是为什么我希望保持事件签名简短,或者不使用FxCop建议使用Generic EventHandler <T>而不是我自己的委托类型。但是,请坚持使用乔恩·斯基特(Jon Skeet)提供的速记版本:)
OregonGhost,

2
如果您拥有ReSharper,它会告诉您长版本过大(通过将其涂成灰色),并且可以使用“快速修复”来再次删除它。
罗杰·利普斯科姆

5

C ++和C#都缺少创建类型的简便方法,该类型在语义上与现有类型相同。我发现这样的“ typedefs”对于类型安全的编程至关重要,而真正的遗憾是C#没有内置它们。void f(string connectionID, string username)到之间的区别void f(ConID connectionID, UserName username)很明显...

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

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

  • 它不适用于原始类型
  • 派生类型仍然可以转换为原始类型,即我们可以将其发送给接收原始类型的函数,这违背了整个目的
  • 我们不能从密封类派生(即许多.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来说却非常冗长。另外,由于要通过其Composed属性来对类进行序列化,因此在序列化(即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) => 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

希望这可以帮助 !


4

您可以使用我创建的一个开源库和一个名为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));
    }
}

LikeType是否可以用于诸如stackoverflow.com/questions/50404586/…之类的复杂项目?我尝试过使用它,但无法获得有效的课程设置。
杰伊·克罗汉

那不是LikeType图书馆的真正意图。LikeType的主要目的是帮助解决Primitive Obsession,因此,它不希望您像包装类型那样传递包装类型。与之类似,如果我制作Age : LikeType<int>了函数Age,则如果我需要,我想确保我的调用者传递的是an Age,而不是an int
马特·克莱因

话虽如此,我想我对你的问题有一个答案,我将在那儿发布。
马特·克莱因

3

下面是它的代码,喜欢!我拿起了那个从dotNetReference键入命名空间线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"};
        }
    }
}

2

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

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
}

1

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.