Reputation: 1015
I have read through some of the other answers to do a dynamic cast and I am not sure they address the situation I want to solve hence the question.
I have an interface
public interface ICustomTransmitter<T> : IDataTransmitter where T : EventArgs
{
event EventHandler<T> DataEvent;
}
and a set of functions that allow me to get the generic type argument at runtime. This is in order to cast to that type and hook up the specific event (this is thought code so please be gentle)
public bool IsTypeOf(Type baseType, Type interfaceType,
out Type argumenType)
{
var interfaces = baseType.GetInterfaces();
argumenType = null;
foreach (Type @interface in interfaces)
{
if (@interface.Name != interfaceType.Name) continue;
if (@interface.IsGenericType)
{
argumenType = @interface.GetGenericArguments()[0];
}
return true;
}
return false;
}
And the function that uses the magic above
Type argument;
var generic = typeof (ICustomTransmitter<>);
if (IsTypeOf(receiver.GetType(),generic ,out argument))
{
var created = generic.MakeGenericType(new[] {argument});
//the line of code missing is below
receiver as created
}
Is it possible to cast the receiver to that created type? Also I need to have a solution that works in both dot net 3.5 and dot net 4.
Upvotes: 3
Views: 7603
Reputation: 2985
Using reflection you can cast from a type which is known only at runtime. By using this pattern - calling a generic method through reflection - you get into a method in which the generic parameter or any class using it is defined.
Here is some code using that pattern for the case above but it can be used in every case where a type or its generic parameter(s) are only known at runtime. One has to be careful of the cost of using reflection OFC.
public void CastToInterfaceTest()
{
var args = new CustomEventArgs { Data = "whatever" };
var instance = new CustomTransmitter();
ProcessGenericEvent(instance, args);
}
public void ProcessGenericEvent(ICustomTransmitter instance, EventArgs args)
{
static MethodInfo AddEventHandlerMethodInfo() =>
typeof(ReflectionCastHelper).GetMethod(nameof(AddEventHandler),
BindingFlags.NonPublic | BindingFlags.Static);
var t = instance.GetType();
var interfaceType = t.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICustomTransmitter<>));
var genericType = interfaceType.GenericTypeArguments[0];
AddEventHandlerMethodInfo()
.MakeGenericMethod(genericType)
.Invoke(null, new object[] { instance, args });
}
private static void AddEventHandler<TEventArgs>(object instance, TEventArgs args)
where TEventArgs : EventArgs, new()
{
var i = instance as ICustomTransmitter<TEventArgs>;
i!.Event += i!.CustomHandler;
i!.RaiseEvent(args);
}
public interface ICustomTransmitter
{
}
public interface ICustomTransmitter<T> : ICustomTransmitter
where T : EventArgs
{
event EventHandler<T> Event;
void RaiseEvent(T eventArgs);
void CustomHandler(object sender, T e);
}
public class CustomEventArgs : EventArgs
{
public string Data { get; set; }
}
public class CustomTransmitter : ICustomTransmitter<CustomEventArgs>
{
public event EventHandler<CustomEventArgs> Event;
public void RaiseEvent(CustomEventArgs eventArgs) => Event!(this, eventArgs);
public void CustomHandler(object sender, CustomEventArgs e) =>
Console.WriteLine($"Got event {e} with data \"{e.Data}\" from {sender}");
}
Upvotes: 0
Reputation: 40838
There is no kind of casting that will do that. Casting is about checking if a runtime type is a known compile time type. You don't even have a compile time type.
What you need to do is use reflection to look for the interface, extract the generic type argument, create a compatible delegate, and hook up the handler for the delegate. You've got the first couple steps with IsOfType
.
When you hook up an event handler you are calling its add
method. The compiler generates a name for this method that is of the form "add_EventName". Here is some sample code that does all that stuff:
using System;
using System.Reflection;
public class Program
{
public static void Main(string[] args)
{
object o = new CustomArgsTransmitter();
// make sure we've got the interface
var interf = o.GetType().GetInterface("ICustomTransmitter`1");
// get the arg type.
var argType = interf.GetGenericArguments()[0];
// create a delegate for the handler based on the arg type above
var handlerMethodInfo = typeof(Program).GetMethod("Handler", BindingFlags.Static | BindingFlags.Public)
var del = Delegate.CreateDelegate(typeof(EventHandler<>).MakeGenericType(argType), handlerMethodInfo);
// Invoke the add method of the event.
o.GetType().InvokeMember("add_DataEvent", BindingFlags.InvokeMethod, null, o, new object[] { del });
// just test code at this point.
// fire event to make sure it is signed up.
// It should print a message to the console.
((CustomArgsTransmitter)o).FireEvent();
}
public static void Handler(object sender, EventArgs e)
{
Console.WriteLine("Got event {0} from {1}", e, sender);
}
}
public interface IDataTransmitter { }
public interface ICustomTransmitter<T> : IDataTransmitter where T : EventArgs
{
event EventHandler<T> DataEvent;
}
public class MyArgs : EventArgs { }
public class CustomArgsTransmitter : ICustomTransmitter<MyArgs>
{
public event EventHandler<MyArgs> DataEvent;
public void FireEvent()
{
DataEvent(this, new MyArgs());
}
}
Upvotes: 2
Reputation: 43218
No. You cannot cast an expression to a type that is not known at compile time. (By "known", I mean resolvable to a Type
whose generic type parameters are closed.)
Having said that, I think it may be possible by using the expression API. The idea is that you would build a lambda expression of the type you determined (which can be strongly-typed), compile it, then execute it on your object to perform the cast. If you 100% need to do this, that's the direction I'd look.
Upvotes: 1