TJR
TJR

Reputation: 3732

Cast to generic interface with where clause

I have an object that I want to call certain methods if it implements specific kinds of interfaces.

I am checking for the generic interfaces like below.

foreach (var iFace in objectType.GetInterfaces())
{
    if (iFace.IsGenericType && iFace.GetGenericTypeDefinition() == typeof(INotify<>))
    {
        //fails to compile because object isn't the correct type.
        doSomethingWithINotifyObject(notifyObject)
    }
    //do other things if different interfaces
}

What I don't know how to do is cast the object so that it is the correct type to call doSomethingWithINotifyObject

The method in this example looks something like this

private void doSomethingWithINotifyObject<T>(INotify<T> notify) where T : EventArgs, INotificationArgs
{
    //do stuff with notification object
}

The INotify interface is defined as

public interface INotify<T> where T: EventArgs, INotificationArgs
{
    //notify stuff
}

Is there any way to cast my object to an INotify<T> where T : EventArgs, INotificationArgs and not care what T actually is?

I tried making a generic method like the following

typeof(MyClass)
    .GetMethod("doSomethingWithINotifyObject")
    .MakeGenericMethod(notifyObject.GetType())
    .Invoke(this, new object[] { notifyObject });

And I get the following runtime exception

Run-time exception (line 13): GenericArguments[0], 'MyClass+doSomethingWithINotifyObject', on 'Void doSomethingWithINotifyObjectT' violates the constraint of type 'T'.

Stack Trace:

[System.Security.VerificationException: Method MyClass.doSomethingWithINotifyObject: type argument 'MyClass+doSomethingWithINotifyObject' violates the constraint of type parameter 'T'.] at System.RuntimeMethodHandle.GetStubIfNeeded(RuntimeMethodHandleInternal method, RuntimeType declaringType, RuntimeType[] methodInstantiation) at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation)

[System.ArgumentException: GenericArguments[0], 'MyClass+doSomethingWithINotifyObject', on 'Void doSomethingWithINotifyObjectT' violates the constraint of type 'T'.] at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e) at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation) at MyClass.Main() :line 13

I have an example of this scenario on .NET fiddle here https://dotnetfiddle.net/ZWMJ4x

Upvotes: 0

Views: 1177

Answers (3)

Camilo Terevinto
Camilo Terevinto

Reputation: 32068

typeof(MyClass)
    .GetMethod("doSomethingWithINotifyObject")
    .MakeGenericMethod(notify.GetType())
    .Invoke(null, new object[] { notify });

Doesn't work because your Notify class is not generic, even though it does implement the generic INotify<FooBarClass>. What you need to pass to MakeGenericMethod is the actual type arguments used by Notify:

typeof(MyClass)
    .GetMethod("doSomethingWithINotifyObject")
    .MakeGenericMethod(iFace.GetGenericArguments())
    .Invoke(null, new object[] { notify });

Upvotes: 1

John Wu
John Wu

Reputation: 52250

It's possible without Reflection if INotify is covariant with respect to T.

Since you have a two-part type constraint, you will need to define a base class that implements both, and derive your INotify<> classes from that.

public interface INotificationArgs
{
}

public interface INotify<out T> where T: EventArgs, INotificationArgs
{
    void PrintName();
}

public class MyEventArgsBase : EventArgs, INotificationArgs {}

public class MyEventArgs1 : MyEventArgsBase {}

public class MyEventArgs2 : MyEventArgsBase {}

public class MyEventArgs3 : MyEventArgsBase {}

class MyClass<T> : INotify<T> where T : EventArgs, INotificationArgs
{
    public string Name { get; set; }

    public MyClass(string name) { Name = name; }

    public void PrintName()
    {
        Console.WriteLine(Name);
    }
}

public class Program
{

    private static void doSomethingWithINotifyObject<T>(INotify<T> notify) where T : EventArgs, INotificationArgs
    {
        notify.PrintName();
    }

    public static void Main()
    {
        object[] tests = new object []
        {
            new MyClass<MyEventArgs1>("1"),
            new MyClass<MyEventArgs2>("2"),
            new MyClass<MyEventArgs3>("3")
        };

        foreach (var o in tests)
        {
            var i = o as INotify<MyEventArgsBase>;
            if (i != null)
            {
                doSomethingWithINotifyObject(i);
            }
        }
    }
}

Output:

1
2
3

Link to DotNetFiddle example

Upvotes: 0

BradleyDotNET
BradleyDotNET

Reputation: 61349

No, you need to know what T is because otherwise the type system can't check if your parameters are the right type, or if your return value types are correct, etc.

You could do everything via reflection and bypass all that, or if you really don't care what T is just put those methods in a generic interface and cast to that (they shouldn't have Ts in them after all).

Upvotes: 1

Related Questions