If a class satisfies a covariant interface with a derived class as type parameter, why is that not enough to satisfy a constraint for the same interface with the base class as type parameter?
Given the following code
public interface ICovariantInterface<out T>
{
T Value { get; }
}
public class Base { public int Value { get; set; } }
public class Sub : Base { public int OtherValue { get; set; } }
public class A : ICovariantInterface<Sub>
{
Sub _sub = new Sub { Value = 1, OtherValue = 2 };
public Sub Value { get { return _sub; } }
}
public class B : A
{
ICovariantInterface<Base> MeAsInterface { get { return this; } }
}
public interface OtherInterface : ICovariantInterface<Base>
{ int ThirdValue { get; } }
public class C : A, OtherInterface
{
ICovariantInterface<Base> MeAsInterface { get { return this; } }
public int ThirdValue { get { return 2; } }
}
Class B works fine - since A satisfies the covariant interface ICovariantInterface with type parameter Sub, it can immediately be converted to the same interface with type parameter Base - but C fails to compile, with error
'CovarianceTest.C' does not implement interface member 'CovarianceTest.ICovariantInterface.Value'. 'CovarianceTest.A.Value' cannot implement 'CovarianceTest.ICovariantInterface.Value' because it does not have the matching return type of 'CovarianceTest.Base'.
How can C fail to satisfy the constraint when it can immediately be converted to the interface specified in the constraint?
This problem has made it quite difficult for me to take full advantage of the possibilites offered by covariance.
Covariance (and contravariance) establish rules for consuming types with type parameters marked with out (or in), in that, if you have an instance of a particular type certain implicit conversions are available to you, as the consumer.
In your example, B is consuming an instance of A (that happens to be itself, but need not have been) at the point at which your allowed cast occurs.
But in your C example, you're attempting to implement an interface. Covariance and Contravariance don't apply here - if the interface states that a method returns a T then the implementation has to provide a method that returns exactly the type substituted for T.
Related
the chosen title for my question isn't quite precise, but I don't know the term I'm looking for or if it is even possible.
What I have in mind is a chain of consumer <- procucer <-product.
A consumer can "consume" producers and producers "produce" products of a certain type. Therefor I wrote:
public interface IProduct
{
string ProductName { get; }
}
public class Product : IProduct
{
public string ProductName { get { return "name of product"; } }
}
public interface IProducer<T>
{
T ProducerProperty { get; set; }
void ProducerMethod();
}
public class Producer<T> : IProducer<T> where T : IProduct
{
public Producer()
{
}
public T ProducerProperty { get; set; }
public void ProducerMethod()
{
}
}
public interface IConsumer<T>
{
T ConsumerProperty { get; set; }
void ConsumerMethod();
}
public class Consumer<T> : IConsumer<T>
{
private U producer; //U should be IProducer<IProduct>, doesen't work
public Consumer(U producer) //U should be IProducer<IProduct>, doesen't work
{
this.producer = producer;
}
public T ConsumerProperty { get; set; }
public void ConsumerMethod()
{
}
}
and the use case:
private IProducer<IProduct> producer; //DeviceManager
private IConsumer<IProducer<IProduct>> consumer; //DeviceViewManager
public MainPage()
{
this.InitializeComponent();
producer = new Producer<IProduct>();
consumer = new Consumer<IProducer<IProduct>>();
}
The consumer class uses the generic "U", which is imaginary at this point. I want the consumer class to use the type U. In the context of the given example you could think of a user who consumes different types of noodles from different manufacrures of noodles.
I want the generic classes be tied to the interfaces rather than to actual classes. But I culdn't manage to achieve this. I tried substituting the interfaces with base classes (e.g.: ProducerBase), but than the actual base classes were needed.
The Problem
If you define the Consumer class like this
public class Consumer<T, U> : IConsumer<T, U>
{
...
}
you get the errors
CS0314 The type 'T' cannot be used as type parameter 'T' in the generic type or method IConsumer<T, U>'. There is not boxing conversion or type parameter conversion from 'T' to ''IProducer'.
and
CS0314 The type 'U' cannot be used as type parameter 'U' in the generic type or method IConsumer<T, U>'. There is not boxing conversion or type parameter conversion from 'U' to ''IProducer'.
What this tells you is that you cannot derive from the IConsumer<T, U> interface, because it has constraints for its generic type parameters:
public interface IConsumer<T, U>
where T : IProducer<U> // T needs to "be" an IProducer<U>
where U : IProduct // U needs to "be" an IProduct
{
...
}
because the interface expects T and U to derive from IProducer<U> and IProduct respectively and the compiler doesn't know that the generic type parameters T and U of the Consumer class can actually be converted to an IProducer<U> and IProduct respectively.
The Solution
Therefore, if you define the Consumer like this and add constraints for T and U
public class Consumer<T, U> : IConsumer<T, U>
where T : IProducer<U>
where U : IProduct
{
...
}
it will work because now you specified that T and U will always be an IProducer<U> and IProduct respectively.
I hope above explanation was understandable; either way, you should also read this for a good understanding of constraints on generic types.
Good day, all.
May I ask you logic why this is not possible?
I'm studying interface & generic now and I think this is possible because Group implements both iPoppable & iPushable. But converting iPoppable to Group is not possible, compiler complains. I'd like to know the logic why this is not possible.
interface iPoppable<out T>{T Pop();}
interface iPushable<in T>{void Push(T ag_t);}
class Program
{
static void Main()
{
iPoppable<Lion> lions = new Group<Lion>();
iPoppable<Animal> animals = lions; //Possible
Group<Lion> lions2 = lions; //Not possible
}
}
class Animal{}
class Lion:Animal{}
class Group<T>:iPoppable<T>, iPushable<T>
{
public void Push(T ag_t){}
public T Pop(){return something;}
}
Alright, step by step.
iPoppable<Lion> lions = new Group<Lion>();
Works, because Group implements iPoppable and generic parameter T is the same.
iPoppable<Animal> animals = lions;
Works, because both of them are iPoppable and Lion derives from Animal. More formally, this is an example of covariance.
An object that is instantiated with a more derived type argument is assigned to an object instantiated with a less derived type argument. Assignment compatibility is preserved.
by Microsoft Docs.
Group<Lion> lions2 = lions;
Does not work, because you assign an interface type to a class type. iPoppable just says that lions has Lion Pop(); method, no more! By saying Group<Lion> lions2 = lions; you claim that lions2 is a full-featured Group object which will have all methods and properties of Group class. Which is not necessarily true, and that's why compiler complains.
You may help compiler by saying
Group<Lion> lions2 = (Group<Lion>)lions;
because you know for a fact that particularly lions, although the type is iPoppable is in fact Group.
To illustrate what the compiler is afraid of, see the following snippet.
interface iPoppable<out T>
{
T Pop();
}
interface iPushable<in T>
{
void Push(T ag_t);
}
class Program
{
static void Main()
{
// Here, we know the truth, so we cast
iPoppable<bool> group = new Group<bool>();
Group<bool> group2 = (Group<bool>)group; // Possible
// What about here? We also convert iPoppable to Group...
iPoppable<bool> notGroup = new NotGroup<bool>();
Group<bool> notGroup2 = (Group<bool>)notGroup; // Bad... Compiler was right...
notGroup2.HelloGroup = true; // HA! Runtime exception.
// That's what compiler was worrying about.
// System.InvalidCastException: Unable to cast object of
// type 'NotGroup`1[System.Boolean]' to type 'Group`1[System.Boolean]
}
}
class Group<T> : iPoppable<T>, iPushable<T>
{
public void Push(T ag_t) { }
public T Pop() { return default(T); }
public bool HelloGroup { get; set; }
}
class NotGroup<T> : iPoppable<T>, iPushable<T>
{
public void Push(T ag_t) { }
public T Pop() { return default(T); }
public bool HelloNotGroup { get; set; }
}
This question already has an answer here:
Generic Type in constructor
(1 answer)
Closed 2 years ago.
I have a generic class. The constructor needs to accept an argument that is another instance of the same class. The problem is that the other instance can have a different generics type.
Looks like C# allows me to have a method with it's own generics type, but this doesn't appear allowed for the constructor.
public class MyClass<T>
{
public MyClass<T2>(MyClass<T2> parent = null)
{
}
// ... Additional stuff
}
The code above tells me T2 is undefined. It doesn't accept it as a method type.
One approach would be to add a second generic type to my class. But this is awkward and, in many cases, the argument will be null and there is not type.
Does anyone see a simple way around this?
Generic constructors aren't allowed. However, you can use a generic factory method instead.
public class MyClass<T>
{
public int Id { get; private set; }
public int? ParentId { get; private set; }
public static MyClass<T> Create(int id)
{
return Create<object>(id, null);
}
public static MyClass<T> Create<T2>(int id, MyClass<T2> parent = null)
{
var current = new MyClass<T>();
current.Id = id;
current.ParentId = parent?.Id;
return current;
}
private MyClass()
{
}
// ... Additional stuff
}
Sample use:
var intClass = MyClass<int>.Create(55);
var charClass = MyClass<char>.Create(234, intClass);
// charClass.ParentId is 55
This is only possible if you do not need to access any generic members of parent outside the factory method. If you do, you'd be better off abstracting it through a non-generic interface or base class.
You are correct. Generic constructors aren't supported.
You could probably try the following:
Create a lower level common interface
public interface IMyClass {
//...some common stuff
IMyClass Parent { get; set; }
}
And use that as the common link between the types
public class MyClass<T> : IMyClass {
public MyClass(IMyClass parent = null) {
Parent = parent;
}
public IMyClass Parent { get; set; }
// ... Additional stuff
}
Lets say I have this arrangement:
public interface ICreatable
{
int CreatedByUserId { get; set; }
}
public class Unicorn : ICreatable
{
public int CreatedByUserId { get; set; }
}
public interface ICrudService<T>
where T : class, ICreatable
{
T DoSomething(T t);
}
public class UnicornService : ICrudService<Unicorn>
{
public Unicorn DoSomething(Unicorn unicorn)
{
var createdByUserId = unicorn.CreatedByUserId;
// ...
return unicorn;
}
}
And use it like so:
static void Main(string[] args)
{
var unicorn = new Unicorn();
var unicornService = new UnicornService();
unicornService.DoSomething(unicorn);
}
This runs fine. However, lets say I want to cast unicornService as it's interface type of ICrudService along with it's generic type to it's interface type as such:
var crudService = unicornService as ICrudService<ICreatable>;
I run into problems. This is how it looks:
unicornService as ICrudService<Unicorn> --> casts is fine
unicornService as ICrudService<ICreatable> --> casts to null
It seems since Unicorn derives from ICreatable and since ICrudService<T> where T: class, ICreatable that it should have no problems working this out. My searches started leading me into Covariance and Contravariances but I'm getting lost at that level.
How can I cast crudService to ICrudService<ICreatable>?
Update:
Using covariance as such:
public interface ICrudService<out T>
Then makes intellisense say "Invalid variance: The type parameter 'T' must be contravariantly valid on 'ICrudService.DoSomething(T)'. 'T' is covariant." How does this work?
An ICrudService<Unicorn> cannot be treated as an ICrudService<ICreatable>.
An ICrudService<Unicorn> object is only allowed to accept parameters of type Unicorn or subtypes of Unicorn, but an ICrudService<ICreatable> can accept a parameter of SomeOtherTypeOfICreatable.
You UnicornService type is allowed to use members specific to Unicorn, not just ICreatable, since that's the type it restricted it's function to. That restriction prohibits it from meeting the more general interface's API.
So, in short, it's not possible.
You should change DoSomething to accept ICreatable instead of T to use out T modifier:
public interface ICrudService<out T>
where T : class, ICreatable
{
T DoSomething(ICreatable t);
}
public class UnicornService : ICrudService<Unicorn>
{
public Unicorn DoSomething(ICreatable t)
{
var unicorn = (Unicorn)t;
var createdByUserId = unicorn.CreatedByUserId; // or t.CreatedByUserId
// ...
return unicorn;
}
}
Please note that this code will throw the exception if non-Unicorn will be passed to UnicornService.
Supposing we have two classes that implement a common interface.
public interface IContract
{
int Type { get; }
}
public class XClass : IContract
{
public int Type { get; set; }
public int X { get; set; }
}
public class YClass : IContract
{
public int Type { get; set; }
public int Y { get; set; }
}
Now, As you know, when we assign a class to the implemented interface, the interface instance contain all the data of the assigned class boxed inside.
IContract ic = new XClass();
There should be a way to detect the datatype of the value within the interface variable ic.
If(ic contains XClass datatype) Then ...
If(ic contains YClass datatype) Then ...
I'd be glad if anyone can help me to detect the datatype assigned to the interface instance. Thanks.
Have you tried:
if (ic is XClass)
You can use the "is" keyword to determine the class. Then there's always .GetType(), but that isn't as clean.
if (ic.GetType() == typeof(XClass))
Also, it's worth mentioning the as keyword. If you want to declare a new object from a current object, guaranteeing it's a specific class:
var d = ic as XClass
If ic isn't XCLass, it will set d to null.
'is' operator is used to get the type of the object dynamically at run time and 'as' is used to type cast between types.
The above examples will be helpful to you.
although if your classes implementation is same irrespective of the type, then you should make use of generics.
if (ic.GetType().Equals(typeof(XClass)))
{ //do something }