Reputation: 14418
class DelegateTest<T> where T: // ....
{
public DelegateTest(DoSomethingDelegate<T> action = null)
{
if (action != null)
_doSomething = action;
else if (typeof(T) == typeof(int))
//_doSomething = DoSomethingInt; // fails
//_doSomething = (DoSomethingDelegate<T>)DoSomethingInt; // fails
//_doSomething = (DoSomethingDelegate<T>)((Delegate)DoSomethingInt); // fails
_doSomething = (val) => DoSomethingInt((int)((object)val)); // too ugly
else // ...
}
public void DoSomethingVeryComplex(T val)
{
// ....
_doSomething(val);
// ....
}
DoSomethingDelegate<T> _doSomething;
static void DoSomethingInt(int val)
{
}
}
delegate void DoSomethingDelegate<T>(T val);
So, the delegate and the method are compatible by the signature, but this assignment doesn't work for some obscure reasons and I'm totally out of ideas how to make it work. Any better ideas other than to create a shim function?
This question is not about how this code can be rewritten in other ways. It's only about how to make this assignment work, and if that's impossible - why.
Upvotes: 1
Views: 163
Reputation: 43738
Similar to John Wu's first solution, but works with the DoSomethingDelegate<T>
as well:
static void Main()
{
new DelegateTest<int>().InvokeDoSomething(1);
new DelegateTest<string>().InvokeDoSomething("Hello");
}
delegate void DoSomethingDelegate<T>(T val);
class DelegateTest<T>
{
public DelegateTest()
{
if (typeof(T) == typeof(int))
_doSomething = new DoSomethingDelegate<T>((Action<T>)(object)DoSomethingInt);
else if (typeof(T) == typeof(string))
_doSomething = new DoSomethingDelegate<T>((Action<T>)(object)DoSomethingString);
else throw new NotSupportedException();
}
DoSomethingDelegate<T> _doSomething;
public void InvokeDoSomething(T x) => _doSomething(x);
void DoSomethingInt(int val) => Console.WriteLine($"DoSomethingInt({val})");
void DoSomethingString(string val) => Console.WriteLine($"DoSomethingString({val})");
}
Output:
DoSomethingInt(1)
DoSomethingString(Hello)
Alternative 1: As mentioned by Enigmativity in a comment, casting the DoSomethingInt
to DoSomethingDelegate<T>
can also be achieved like this:
public DelegateTest()
{
if (typeof(T) == typeof(int))
_doSomething = (DoSomethingDelegate<T>)(Delegate)(DoSomethingDelegate<int>)DoSomethingInt;
else if (typeof(T) == typeof(string))
_doSomething = (DoSomethingDelegate<T>)(Delegate)(DoSomethingDelegate<string>)DoSomethingString;
else throw new NotSupportedException();
}
It also makes the invocation of the delegate faster by about 20% compared to my original suggestion, for a reason explained by Matthew Watson here. My original suggestion involves two method invocations instead of one.
Alternative 2: A variant of John Wu's second solution, using the switch
statement instead of if
+as
:
public DelegateTest()
{
switch (this)
{
case DelegateTest<int> self: self._doSomething = DoSomethingInt; break;
case DelegateTest<string> self: self._doSomething = DoSomethingString; break;
default: throw new NotSupportedException();
}
}
This one is type safe. You can't assign accidentally the wrong method to the wrong type. Invoking the delegate is as fast as the previous Alternative 1.
Upvotes: 2
Reputation: 52280
Since you know T
, you can cast this
to the specific generic type (DelegateTest<int>
). Once you have that reference, you can use it for type-safe and type-specific access to its methods, properties, and fields, even private ones.
class DelegateTest<T>
{
public DelegateTest()
{
if (typeof(T) == typeof(int))
{
var self = this as DelegateTest<int>; //Magic!!
self._doSomething = DoSomethingInt;
}
}
DoSomethingDelegate<T> _doSomething;
public void InvokeDoSomething(T x) => _doSomething(x);
static void DoSomethingInt(int val)
{
Console.WriteLine("Hello. The argument was {0}", val);
}
}
delegate void DoSomethingDelegate<T>(T val);
Quick test:
public class Program
{
public static async Task Main()
{
var o = new DelegateTest<int>();
o.InvokeDoSomething(1);
}
}
Output:
Hello. The argument was 1
Upvotes: 3
Reputation: 117124
The issue with the assignment in the first place, is that you're in scope of DelegateTest<T>
where T
can be any type and you're asking the compiler to allow something of type DoSomethingDelegate<int>
to be assigned to a DoSomethingDelegate<T>
. That's fine if T
is int
at run-time, but the compiler doesn't know that. It's an illegal cast, despite the run-time check for the type.
The best you can do is make your code more resilient to coding errors. Here's variation on Theo's answer that makes the code more strongly-typed. It's probably the best you're going to get without going to reflection.
static void Main()
{
new DelegateTest<int>().InvokeDoSomething(1);
new DelegateTest<string>().InvokeDoSomething("Hello");
}
delegate void DoSomethingDelegate<T>(T val);
class DelegateTest<T>
{
private Dictionary<Type, Delegate> _delegates = new Dictionary<Type, Delegate>();
private void Register<K>(DoSomethingDelegate<K> @delegate)
{
_delegates[typeof(K)] = @delegate;
}
public DelegateTest()
{
this.Register<int>(DoSomethingInt);
this.Register<string>(DoSomethingString);
if (_delegates.ContainsKey(typeof(T)))
{
_doSomething = (DoSomethingDelegate<T>)_delegates[typeof(T)];
}
else throw new NotSupportedException();
}
DoSomethingDelegate<T> _doSomething;
public void InvokeDoSomething(T x) => _doSomething(x);
void DoSomethingInt(int val) => Console.WriteLine($"DoSomethingInt({val})");
void DoSomethingString(string val) => Console.WriteLine($"DoSomethingString({val})");
}
That outputs:
DoSomethingInt(1)
DoSomethingString(Hello)
Upvotes: 0
Reputation: 52280
You can cast and assign the delegate directly if you use Action<T>
instead of your custom delegate.
class DelegateTest<T>
{
public DelegateTest()
{
if (typeof(T) == typeof(int))
{
_doSomething = (Action<T>)(object)DoSomethingInt;
}
}
Action<T> _doSomething;
public void InvokeDoSomething(T x) => _doSomething(x);
static void DoSomethingInt(int val)
{
Console.WriteLine("Hello world! The argument was {0}", val);
}
}
Quick test:
public class Program
{
public static async Task Main()
{
var o = new DelegateTest<int>();
o.InvokeDoSomething(1);
}
}
Output:
Hello world! The argument was 1
Upvotes: 2