Equivalent of typedef in C#
C#TypedefC# Problem Overview
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
C# Solutions
Solution 1 - C#
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;
:)
Solution 2 - C#
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.
Solution 3 - C#
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.
Solution 4 - C#
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 !
Solution 5 - C#
I think there is no typedef. You could only define a specific delegate type instead of the generic one in the GenericClass
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.
Solution 6 - C#
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
};
Solution 7 - C#
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));
}
}
Solution 8 - C#
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"};
}
}
}
Solution 9 - C#
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.
Solution 10 - C#
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
}
Solution 11 - C#
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#.
Solution 12 - C#
I'd do
using System.Collections.Generic;
global using CustomerList = List<Customer>;