Reputation: 693
I have the following conceptual model:
public interface IFoo<out T>
{
T Data { get; }
}
public struct Foo<T> : IFoo<T>
{
public Foo(T data) : this()
{
Data = data;
}
public T Data { get; private set; }
}
public class FooService<T>
{
...
public Foo<T> Get(string id)
{
...
}
}
Then I try to use it in a way that conceptually is equivalent to this:
// Create and register a few FooService instances
ServiceLocator.Register(new FooService<DateTime>(), "someServiceId");
ServiceLocator.Register(new FooService<double?>(), "anotherServiceId");
// Retrieve a particular FooService instance and call the Get method
var fooService = (FooService<object>)ServiceLocator.Get("someServiceId");
var foo = fooService.Get("someFooId");
I want to use the Get() method on a FooService instance - no matter what type the selected FooService instance returns. However, this code results in the following exception:
Unable to cast object of type 'WindowsFormsApplication7.FooService`1[System.DateTime]' to type 'WindowsFormsApplication7.FooService`1[System.Object]'.
Any suggestions on how to solve this problem would be greately appreciated.
You could argue, why I made the FooService generic in the first place. However, this is done to secure type safety in a type safe environment. In this particular case, though, the FooService shall be used in a Web API controller to serve various types of Foo. It shall return a response with Foo of T disregarding the type of T.
Upvotes: 6
Views: 1220
Reputation: 693
I made an example that actually works for reference types, but - as stated by @Martin - can never work for value types, as they do not support variance. The where T : class
constraint ensures that only reference types are accepted.
public interface IFoo<out T> where T : class
{
T Data { get; }
}
public struct Foo<T> : IFoo<T> where T : class
{
public Foo(T data) : this()
{
Data = data;
}
public T Data { get; private set; }
}
public interface IFooService<out T> where T : class
{
IFoo<T> Get(string id);
}
public class FooService<T> : IFooService<T> where T : class
{
public IFoo<T> Get(string id)
{
return null;
}
}
Usage:
ServiceLocator.Register(new FooService<Bar>(), "ServiceId");
// OK because Bar is a reference type
var fooService = (IFooService<object>)ServiceLocator.Get("ServiceId");
var foo = fooService.Get("FooId");
Upvotes: 0
Reputation: 2737
If possible, you could adjust your model like this:
public interface IFoo
{
object Data { get; }
}
public interface IFoo<T> : IFoo
{
new T Data { get; }
}
public class Foo<T> : IFoo<T>
{
public Foo(T data) { Data = data; }
public T Data { get; private set; }
object IFoo.Data { get { return Data; } }
}
public interface IFooService
{
IFoo Get(string id);
}
public interface IFooService<T> : IFooService
{
new IFoo<T> Get(string id);
}
public class FooService<T> : IFooService<T>
{
public IFoo<T> Get(string id) { return null; }
IFoo IFooService.Get(string id) { return Get(id); }
}
which would enable you to do
ServiceLocator.Register(new FooService<DateTime>(), "someServiceId");
ServiceLocator.Register(new FooService<double?>(), "anotherServiceId");
// Retrieve a particular FooService instance and call the Get method
IFooService fooService = (IFooService)ServiceLocator.Get("someServiceId");
IFoo foo = fooService.Get("someFooId");
object data = foo.Data;
Upvotes: 2
Reputation: 12954
I think you have already given the correct answer:
FooService<double?>
can never be casted to FooService<object>
, and
FooService<DateTime>
can never be casted to FooService<object>
.
Using covariance or contravariance does not change that. The page https://msdn.microsoft.com/en-us/library/dd997386.aspx states: Value types also do not support variance.
And since double?
and DateTime
are value types, this can never work.
Upvotes: 7