user845392
user845392

Reputation: 563

Generic Type casting exception

public interface IRule <T> 
{
     T Execute();
}

public class Rule : IRule<int>
{
    public int Execute()
    {
       return 10;
    }
}

IRule<int> rule = new Rule();
var genericRule = (IRule<object>) rule;
//Exception has occurred: CLR/System.InvalidCastException An exception of ype 'System.InvalidCastException' occurred in test.dll but was not handled in user code: 'Unable to cast object of type 'test.GenericCasting.Rule' to type test.GenericCasting.IRule`1[System.Object]'.'

In business logic, I need to load all Rules objects through reflection and don't have the knowledge of the type used in the Rule object. Any idea how to solve this?

Upvotes: 0

Views: 473

Answers (2)

Gy&#246;rgy Kőszeg
Gy&#246;rgy Kőszeg

Reputation: 18013

IRule<object> is not a base type of IRule<int> so the cast does not work.

Starting with .NET 4 interfaces can be marked covariant but that can be used only in special cases: the generic argument must be used in output position only (return value) and only reference types can be cast to each other. In your case the first condition is met but the second one is not.

A possible solution for this case is to introduce a nongeneric base type. This is how a List<T> of an unknown type argument can be used as an IList as well, for example.

public interface IRule 
{
    object Execute();
}

public interface IRule<T> : IRule
{
    new T Execute();
}

Implementation:

public class Rule : IRule<int>
{
    // this will be the public implementation (preferred way)
    public int Execute() => 42;

    // the nongeneric implementation if the object is cast to IRule
    object IRule.Execute() => Execute();
}

And now this will work:

IRule<int> rule = new Rule();
// ...
IRule anyRule = rule;

Upvotes: 2

felix-b
felix-b

Reputation: 8498

In order to perform typecast in your example, there are two conditions that have to be met:

  1. IRule<T> must be co-variant, that is, IRule<out T>
  2. Co-variance only works with reference types as T, that is, you cannot use int for T, but you can use string for example.

A working example:

public interface IRule <out T> 
{
    T Execute();
}

public class Rule : IRule<string> // T must be a reference type
{
    public string Execute()
    {
        return "10";
    }
}

//....

IRule<string> rule = new Rule();
var genericRule = (IRule<object>) rule;

EDIT

As @Servy correctly mentioned, I explained why the original code won't work, but I didn't explain how the original problem can be solved.

Here is how:

// define a separate interface for non-typed invocation
public interface IRule
{
    object ExecuteGeneric();
}

public interface IRule<T> : IRule 
{
    T Execute();
}

// every rule implements both typed and non-typed invocation interface
public class Rule : IRule<int>
{
    public int Execute()
    {
        return 10;
    }

    object IRule.ExecuteGeneric()
    {
        return Execute();
    }
}

//.....

IRule<int> rule = new Rule();
IRule genericRule = rule;
// perform non-typed invocation on rules of any T
var genericResult = genericRule.ExecuteGeneric();

Upvotes: 2

Related Questions