389

Is there a typedef equivalent in C#, or someway to get some sort of similar behaviour? I've done some googling, but everywhere I look seems to be negative. Currently I have a situation similar to the following:

class GenericClass<T> 
{
    public event EventHandler<EventData> MyEvent;
    public class EventData : EventArgs { /* snip */ }
    // ... snip
}

Now, it doesn't take a rocket scientist to figure out that this can very quickly lead to a lot of typing (apologies for the horrible pun) when trying to implement a handler for that event. It'd end up being something like this:

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

Except, in my case, I was already using a complex type, not just an int. It'd be nice if it were possible to simplify this a little...

Edit: ie. perhaps typedefing the EventHandler instead of needing to redefine it to get similar behaviour.

bacar
  • 9,761
  • 11
  • 55
  • 75
Matthew Scharley
  • 127,823
  • 52
  • 194
  • 222

13 Answers13

405

No, there's no true equivalent of typedef. You can use 'using' directives within one file, e.g.

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

but that will only impact that source file. In C and C++, my experience is that typedef is usually used within .h files which are included widely - so a single typedef can be used over a whole project. That ability does not exist in C#, because there's no #include functionality in C# that would allow you to include the using directives from one file in another.

Fortunately, the example you give does have a fix - implicit method group conversion. You can change your event subscription line to just:

gcInt.MyEvent += gcInt_MyEvent;

:)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 12
    I always forget that you can do this. Maybe because Visual Studio suggests the more verbose version. But I'm fine with pressing TAB twice instead of typing the handler name ;) – OregonGhost Oct 02 '08 at 09:25
  • 12
    In my experience (which is scarce), you have to specify the fully qualified type name, for instance: `using MyClassDictionary = System.Collections.Generic.Dictionary;` Is it correct? Otherwise it doesn't seem to consider the `using` definitions above it. – tunnuz Apr 23 '10 at 10:04
  • 4
    I couldn't convert `typedef uint8 myuuid[16];` through "using" directive. `using myuuid = Byte[16];` doesn't compile. `using` can be used just for creating **type** aliases. `typedef` seems to be much more flexible, since it can create an alias for a whole declaration (including array sizes). Is there any alternative in this case? – natenho Aug 26 '14 at 18:09
  • 6
    @natenho: Not really. The closest you could come would be to have a struct with a fixed-size buffer, probably. – Jon Skeet Aug 26 '14 at 18:10
  • 1
    @tunnuz Unless you specify it inside a namespace – John Smith Jan 08 '17 at 13:12
  • Too bad I can't do something like `typedef String_ = System.String?;`. I was hoping I could do conditional compilation based on whether or not nullable references are supported by the compiler (something like `#if NULLABLE_ENABLED ; using T_ = T?; #else ; using T_ = T;`). – geekley Dec 04 '20 at 18:29
  • Should probably be updated to include the new "global using"? – fretje Jan 05 '22 at 14:46
  • @fretje: I don't generally go over my answers every time there's a new version of C#. There just isn't time. I think anyone reading a 13-year-old answer should understand that there may be newer options. – Jon Skeet Jan 05 '22 at 14:51
  • @Jon: Sure I understand that. My comment wasn't necessarily aimed at you... Just ran across this answer when investigating something like this, and it turns out, global usings together with an entry in Directory.build.props gives you actual *global* typedefs... (or at least C#'s version of typedefs) ;-) – fretje Jan 12 '22 at 16:18
  • Circa 2021, C# 10 (.NET 6) introduced the typedef-like `global using Identifier = Namespace.Type` syntax, but it requires the `Preview` langversion. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/globalusingdirective#global-using-alias-directives – Noah Sherwin Jul 13 '23 at 07:36
43

Jon really gave a nice solution, I didn't know you could do that!

At times what I resorted to was inheriting from the class and creating its constructors. E.g.

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

Not the best solution (unless your assembly gets used by other people), but it works.

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
Jonathan C Dickinson
  • 7,181
  • 4
  • 35
  • 46
  • 46
    Definitely a good method, but keep in mind that those (annoying) sealed types exist, and it won't work there. I really wish C# would introduce typedefs already. It's a desperate need (especially for C++ programmers). – MasterMastic Nov 15 '12 at 03:47
  • 1
    I've created a project for this situation called LikeType which wraps the underlying type rather than inheriting from it. It will also implicitly convert _TO_ the underlying type, so you could use something like `public class FooList : LikeType> { ... }` and then use it anywhere you would expect a `IReadOnlyList`. [My answer](http://stackoverflow.com/a/35973121/1672027) below shows more detail. – Matt Klein Mar 16 '16 at 19:01
  • 3
    It also won't infer the type `Foo` if passed to e.g. template method that accepts `List`. With proper typedef it would be possible. – Aleksei Petrenko Apr 26 '17 at 17:57
22

If you know what you're doing, you can define a class with implicit operators to convert between the alias class and the actual class.

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

I don't actually endorse this and haven't ever used something like this, but this could probably work for some specific circumstances.

palswim
  • 11,856
  • 6
  • 53
  • 77
  • Thanks @palswim, I got here looking for something like "typedef string Identifier;" so your suggestion may be just what I need. – yoyo May 31 '13 at 15:47
  • 1
    I use this approach a lot in database systems so that while a CustomerId and OrderId are 32-bit integer values that would normally be `Int32`, they’re represented inside the program as distinct types so no-one will accidentally `GetCustomerById( orderId )`. That said, use a `struct`, not a `class` to avoid unnecessary heap allocations. – Dai Apr 24 '21 at 22:57
15

Both C++ and C# are missing easy ways to create a new type which is semantically identical to an exisiting type. I find such 'typedefs' totally essential for type-safe programming and its a real shame c# doesn't have them built-in. The difference between void f(string connectionID, string username) to void f(ConID connectionID, UserName username) is obvious ...

(You can achieve something similar in C++ with boost in BOOST_STRONG_TYPEDEF)

It may be tempting to use inheritance but that has some major limitations:

  • it will not work for primitive types
  • the derived type can still be casted to the original type, ie we can send it to a function receiving our original type, this defeats the whole purpose
  • we cannot derive from sealed classes (and ie many .NET classes are sealed)

The only way to achieve a similar thing in C# is by composing our type in a new class:

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

While this will work it is very verbose for just a typedef. In addition we have a problem with serializing (ie to Json) as we want to serialize the class through its Composed property.

Below is a helper class that uses the "Curiously Recurring Template Pattern" to make this much simpler:

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

}

With Composer the above class becomes simply:

sealed Class SomeTypeTypeDef : Composer<SomeTypeTypeDef, SomeType> {
   public SomeTypeTypeDef(SomeType composed) : base(composed) {}

   // proxy the methods we want
   public void Method() => Composed.Method();
}

And in addition the SomeTypeTypeDef will serialize to Json in the same way that SomeType does.

Hope this helps !

kofifus
  • 17,260
  • 17
  • 99
  • 173
10

With C# 10 you can now do

global using Bar = Foo

Which works like a typedef within the project.

I haven't tested it in depth, so there might be quirks.

I'm using it like

global using DateTime = DontUseDateTime

Where DontUseDateTime is a struct marked Obsolete, to force people to use NodaTime.

JJJ
  • 509
  • 1
  • 6
  • 14
  • This is not a typedef, since if you define an extension method that works on Bar it will also work on Foo. This is not what one would expect from a type-driven development perspective. – Cosmin Sontu Apr 13 '23 at 23:35
  • This can only define global type, but not replace a type inside a class to another. – mugi Jun 14 '23 at 09:52
6

I think there is no typedef. You could only define a specific delegate type instead of the generic one in the GenericClass, i.e.

public delegate GenericHandler EventHandler<EventData>

This would make it shorter. But what about the following suggestion:

Use Visual Studio. This way, when you typed

gcInt.MyEvent += 

it already provides the complete event handler signature from Intellisense. Press TAB and it's there. Accept the generated handler name or change it, and then press TAB again to auto-generate the handler stub.

OregonGhost
  • 23,359
  • 7
  • 71
  • 108
  • 2
    Yea, that's what I did to generate the example. But coming back to look at it again AFTER the fact can still be confusing. – Matthew Scharley Oct 02 '08 at 09:25
  • I know what you mean. That's why I like to keep my event signatures short, or get away from the FxCop recommendation to use Generic EventHandler instead of my own delegate type. But then, stick with the short-hand version provided by Jon Skeet :) – OregonGhost Oct 02 '08 at 09:28
  • 2
    If you've got ReSharper, it'll tell you that the long version is overkill (by colouring it in grey), and you can use a "quick fix" to get rid of it again. – Roger Lipscombe Oct 02 '08 at 09:38
6

C# supports some inherited covariance for event delegates, so a method like this:

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

Can be used to subscribe to your event, no explicit cast required

gcInt.MyEvent += LowestCommonHander;

You can even use lambda syntax and the intellisense will all be done for you:

gcInt.MyEvent += (sender, e) =>
{
    e. //you'll get correct intellisense here
};
Keith
  • 150,284
  • 78
  • 298
  • 434
  • I seriously need to getting around to having a good look at Linq... for the record though, I was building for 2.0 at the time (in VS 2008 though) – Matthew Scharley Oct 02 '08 at 13:25
  • Oh, also, I can subscribe fine, but then to get at the event arguments I need an explicit cast, and preferably type checking code, just to be on the safe side. – Matthew Scharley Oct 02 '08 at 13:26
  • 9
    The syntax is correct, but I wouldn't say it's "Linq syntax"; rather it is a lambda expression. Lambdas are a supporting feature that make Linq work, but are completely independent of it. Essentially, anywhere you can use a delegate, you can use a lambda expression. – Scott Dorman Oct 02 '08 at 14:15
  • Fair point, I should have said lambda. A delegate would work in .Net 2, but you'd need to explicitly declare the nested generic type again. – Keith Oct 02 '08 at 16:38
5

You can use an open source library and NuGet package called LikeType that I created that will give you the GenericClass<int> behavior that you're looking for.

The code would look like:

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));
    }
}
Matt Klein
  • 7,856
  • 6
  • 45
  • 46
  • would LikeType work for something complex like https://stackoverflow.com/questions/50404586/is-there-a-way-to-use-a-friendy-name-for-this-class-interface?noredirect=1#comment87825750_50404586 ? I've tried playing with it and can't get a class setup that works. – Jay Croghan May 18 '18 at 07:28
  • That's not really the intent of the `LikeType` library. `LikeType`'s primary purpose is to help with [Primitive Obsession](http://wiki.c2.com/?PrimitiveObsession), and as such, it doesn't want you to be able to pass around the wrapped type like it was the wrapper type. As in, if I make `Age : LikeType` then if my function needs an `Age`, I want to ensure my callers are passing an `Age`, not an `int`. – Matt Klein May 18 '18 at 14:37
  • That being said, I think I have an answer to your question, which I'll post over there. – Matt Klein May 18 '18 at 14:38
5

Here is the code for it, enjoy!, I picked that up from the dotNetReference type the "using" statement inside the namespace line 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"};
        }
    }
}
shakram02
  • 10,812
  • 4
  • 22
  • 21
5

I'd do

using System.Collections.Generic;
global using CustomerList = List<Customer>;
OS Freak
  • 51
  • 2
  • 2
3

For non-sealed classes simply inherit from them:

public class Vector : List<int> { }

But for sealed classes it's possible to simulate typedef behavior with such base class:

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
}
Vlad Rudenko
  • 2,363
  • 1
  • 24
  • 24
  • Thx .. this was helpful in my case as Blazor keeps me from the use of "using ..."-Method shown above – eagle275 Jan 25 '23 at 13:41
1

The best alternative to typedef that I've found in C# is using. For example, I can control float precision via compiler flags with this code:

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

Unfortunately, it requires that you place this at the top of every file where you use real_t. There is currently no way to declare a global namespace type in C#.

Aaron Franke
  • 3,268
  • 4
  • 31
  • 51
0

Since the introduction of C# 10.0, we now have the global using directive.

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

This introduces CustomerList as alias of List<Customer> on a global scope (throughout the whole project and all references to it).

Though I would have liked to be able to limit its scope (say for instance 'internal using') this does actually do a terrific job of fulfilling a typedef variant in C#.

Rob Vermeulen
  • 1,910
  • 1
  • 15
  • 22