This question already has an answer here:
Parameter must be input-safe error
(1 answer)
Closed 4 years ago.
I have two interfaces that are both covariant, with both being passed in to each other like so:
public interface Perfomer<in T>
{
void Perform(T t, Tracer<T> tracer);
}
public interface Tracer<in T>
{
void Notify();
}
However even though both interfaces are marked covariant, and T is only ever being used as input, I'm still getting the error:
"Invalid variance: The type parameter 'T' must be covariantly valid on
'Perfomer<T>.Do(T, Tracer<T>)'. 'T' is contravariant. [_Console].
Any ideas why having covariant interface parameter using the same type makes T contravariant?
Edit
(Sorry, I am new to StackOverflow, based on the answers I realize I should've been more exact in my question, I had just tried to eliminate as much noise as possible to a single error).
The code actually has two interfaces with generally similar interfaces:
public interface Performer<in T>
{
bool Perform(T t, Tracer<T> tracer = null);
}
public interface Tracer<in T>
{
void Notify(Performer<T> performer, T t, ref bool success);
}
It's purpose is to allow the an optional "tracer" to see things happen/modify the results of a performer.
When you declare that Performer is contravariant, you are declaring that anything a Performer does to a T can also be done to a more specific version of T. For example, an action that acts on a object can be given a string, and it'll just act as if that string is an object.
So for example you could do this, because all streams support Length:
class MyClass : Performer<Stream>
{
void Perform(Stream t)
{
Console.WriteLine(t.Length)
}
}
Performer<FileStream> p = new MyClass();
p.Perform(new FileStream());
But you can't do this, because you gave it a class that doesn't support IsAsync:
class MyClass : Performer<FileStream>
{
void Perform(Stream t)
{
Console.WriteLine(t.IsAsync)
}
}
Performer<Stream> p = new MyClass();
p.Perform(new Stream()); //Stream isn't good enough; it has to be a FileStream, since it needs IsAsync
So far so good. Now let's add in that second parameter:
class MyClass : Performer<Stream>
{
void Perform(Stream t, Tracer<Stream> tracer)
{
Console.WriteLine(tracer.Notify())
}
}
In order for this to work, the contravariance has to work. If the contravariance works, it means that Perform can store a Tracer<FileStream> (which you pass in) in a variable that is typed as a Tracer<Stream> (which is how it is implemented). That means that Tracer must be covariant with respect to its type argument.
So you can fix your code by changing in to out, like so:
public interface Performer<in T>
{
void Perform(T t, Tracer<T> tracer);
}
public interface Tracer<out T> //out instead of in
{
void Notify();
}
From what you've provided I'd avoid the issue all together, Modify the Tracer interface to remove the T because it's not needed:
public interface INotify
{
void Notify();
}
Then just take in an the new interface in your performer
public interface Perfomer<in T>
{
void Perform(T t, INotify entity);
}
PS: there might be a type in your interface name Perfomer => Performer
Just modifyTracer<in T> to Tracer (non-generic) and define void Perform(T t, Tracer tracer);.
Your code was not using T in Tracer anyways.
Since you edited your question with new details, the alternative fix is to remove in from generics definition. You don't need it. Another way to achieve what you want is following:
public interface Performer<T>
{
bool Perform(T t, Tracer tracer = null);
}
public interface Tracer
{
bool Notify<T>(Performer<T> performer);
}
Note: drop ref bool and return bool instead
Related
I’m having some problems with interfaces and co/contravariance and I’m having some problems. Imagine a structure like below (excuse any obvious mistakes, I’m in mobile at the moment)
public interface IDelimitedFileReader<T>
{
IEnumerable<T> Read(string file);
}
public interface IMapper<T> where T : IManifestItem
{
MappedRecord Map(IEnumerable<T> items);
}
public interface IProfile<T> where T : IManifestItem
{
IDelimitedFileReader<T> Reader { get; }
IMapper<T> Mapper { get; }
}
public class ProfileImpl : IProfile<ManifestItemImpl>
{
IDelimitedFileReader<ManifestItemImpl> Reader => new DelimitedFileReaderImpl<ManifestItemImpl>();
IMapper<ManifestItemImpl> Mapper => new MapperImpl<ManifestItemImpl>();
}
public static class ProfileRetriever
{
public static IProfile<IManifestItem> GetProfile()
{
return new ProfileImpl();
}
}
However my GetProfile method complains that the return types do not match. I believe this is because the IProfile interface needs to be covariant AND contravariant - if I remove the Mapper property on the interface and implementation, and change T to be ‘in’ in IProfile, it works. If I remove Reader, it works if I make T to ‘out’. I need to do both but obviously can’t!
Am I being really stupid or is what I want impossible? Thanks!
T is contravariant in IMapper but covariant in IProfile and IDelimitedFileReader. The definition of something covariant and contravariant is invariant, which means that T doesn't allow any type variance; similar to IList. Your current set up will not work.
The issue should be made clear if you declare the desired variance in all interfaces (as you should do, otherwise you'll get cryptic error messages because the compiler can't do any better):
interface IDelimitedFileReader<out T> { /*...*/ }
interface IMapper<in T> { /*...*/ }
interface IProfile<out T> { /*...*/ } //at first glance it looks covariant
If you try to compile this, the compiler will give you a precise error of whats wrong.
If you declare T invariant in IProfile: interface IProfile<T> { //... } then the error will go away and the code should compile but your stuck with an invariant interface.
Here is a piece of my code:
public interface IA<in TInput>
{
void Method(IB<TInput> entities);
}
public interface IB<in T> { }
I can't figure out why I get following compile error:
"Parameter must be input-safe. Invalid variance: The type parameter |TInput| must be contravariantly valid on "IB< in T>".
Any help will be appreciated.
The designator of contravariance in C# (i.e. in) is intuitive only at the immediate level, when you make a method that "takes in" a parameter of generic type. Internally, however, contravariance means an inversion of a relation (Q&A with an explanation) so using in inside IA makes it incompatible with IB.
The problem is best illustrated with an example. Consider class Animal and its derived class Tiger. Let's also assume that IB<T> has a method void MethodB(T input), which is called from IA's Method:
class A_Impl<T> : IA<T> {
T data;
public void Method(IB<TInput> entities) {
entities.MethodB(data);
}
}
Declaring IA<in TInput> and IB<in TInput> means that you can do
IA<Animal> aForAnimals = new A_Impl<Animal>();
IA<Tiger> aForTigers = aForAnimals;
IA<in TInput> has a method that takes IB<TInput>, which we can call like this:
aForTigers.Method(new B_Impl<Tiger>());
This is a problem, because now A_Impl<Animal> passes an Animal to MethodB of an interface that expects a Tiger.
You would have no problem with IB<out T>, though - both with covariance and contravariance:
public interface IB<out T> {
// ^^^
}
// This works
public interface IA<in TInput> {
void Method(IB<TInput> x);
}
// This works too
public interface IC<out TInput> {
void Method(IB<TInput> x);
}
The following does not compile on line fm.AddFoo(new StringFoo()); with the error message:
Argument 1: cannot convert from 'ClassLibrary2.StringFoo' to 'ClassLibrary2.IFoo'
This seems logical to me since string inherits from object.
public interface IFoo<T>
{
void Handle(T value);
}
public class StringFoo : IFoo<string>
{
public void Handle(string value)
{ }
}
public class ObjectFoo : IFoo<object>
{
public void Handle(object value)
{ }
}
public class FooManager
{
private readonly List<IFoo<object>> _foos;
public FooManager()
{
_foos = new List<IFoo<object>>();
}
public void AddFoo(IFoo<object> foo)
{
_foos.Add(foo);
}
}
public class Bad
{
public Bad()
{
var fm = new FooManager();
fm.AddFoo(new StringFoo()); \\ This does not compile
}
}
Thanks
Although it may seem like IFoo is a subclass of IFoo it is not. When you close IFoo<> to a specific type is is not creating a subclass of IFoo from IFoo, they are seperate and distinct types with no common hierarchy.
If you could make your IFoo<> interface covariant it would work, that is if you were allowed to change the declaration of it into:
public interface IFoo<out T>
(note the out). Because with covariance any IFoo<string> would also be an IFoo<object> because string is a reference type and derives from object.
But: A member of IFoo<>, the Handle method, uses the type parameter in a contravariant manner. So your interface cannot be declared covariant (out). (It could be declared contravariant (in) but that goes in the wrong direction for your example above.)
Read up on covariance and contravariance in generics.
The fundamental problem here is that your StringFoo handles only strings. Therefore it can never be used as an IFoo<object> because then you could pass for example a Giraffe instance (Giraffe derives from object, so a Giraffe is an object) into the StringFoo, and that is impossible when its Handle takes a string.
I have created this interface for my Repositories.
public interface IRepository<T, in TKey> where T: class
{
IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
IEnumerable<T> FindAll();
T FindSingle(TKey id);
void Create(T entity);
void Delete(T entity);
void Update(T entity);
}
The FindSingle method accepts an ID, which will be used for searching on Primary Key. By using in I expected that I would only be allowed to pass a reference type as TKey. Out of curiosity I decided to create a concrete class and specify it as an int, so I could see the exception.
I looked up MSDN and it specifies this should not work
Covariance and contravariance in generic type parameters are supported for reference types, but they are not supported for value types.
The class I created looks like this
public class ProjectRepository : IRepository<Project,int>
{
public IEnumerable<Project> Find(Expression<Func<Project, bool>> predicate)
{
throw new NotImplementedException();
}
public IEnumerable<Project> FindAll()
{
throw new NotImplementedException();
}
public Project FindSingle(int id)
{
throw new NotImplementedException();
}
public void Create(Project entity)
{
throw new NotImplementedException();
}
public void Delete(Project entity)
{
throw new NotImplementedException();
}
public void Update(Project entity)
{
throw new NotImplementedException();
}
}
Why did I not get an exception on build having specified TKey as a value type? Also, If I removed the in from my parameter what have I lost? the MSDN document says that the contravariance allows using a less derived type, but surely by removing in I can pass any type in as it is still generic.
This is maybe displaying a lack of understanding on contravariance and covariance but it has me a little confused.
Covariance and contravariance don't make as much sense on value types, because they are all sealed. Though it's not clear from the documentation, it is valid to use a struct as a co/contravariant type, it's just not always useful. The documentation you reference is most likely referring to that the following is not valid:
public struct MyStruct<in T>
Contravariance means that you can do something like the following example:
IRepository<string, Base> b = //something
IRepository<string, Derived> d = b;
Since there's nothing that derives from int, you can use an IRepository<string, int>, but only as an IRepository<string, int>.
Covariance means that you can do the reverse, e.g. IEnumerable<T> is out T, which is covariant. You can do the following:
IEnumerable<Derived> d = //something
IEnumerable<Base> b = d;
If you're trying to restrict both TKey and T to classes (reference types), you should include a second restriction:
public interface IRepository<T, in TKey>
where T : class
where TKey : class
Indeed, you are missing the whole point of co- and contravariance :-) It is about being able to assign a variable of a generic type to another variable of the same generic type but with differing generic type argument(s) that are related to the ones used in the source.
Depending on whether the generic type parameter is co- or contravariant, different assignments are allowed.
Assume the following interface:
public interface IRepository<in T>
{
void Save(T value);
}
Additionally, assume the following interface along with a value type and a reference type that implement it:
public interface IBar
{
}
public struct BarValueType : IBar
{
}
public class BarReferenceType : IBar
{
}
Finally, assume two variables:
IRepository<BarReferenceType> referenceTypeRepository;
IRepository<BarValueType> valueTypeRepository;
Contravariance now means that you can assign an instance of IRepository<IBar> to the variable referenceTypeRepository, because BarReferenceType implements IBar.
The section from the MSDN you quote simply means that the assignment of an instance of IRepository<IBar> to valueTypeRepository is not legal, although BarValueType also implements IBar.
There is no problem in implementing your interface with a value type. You will only get an error when trying to assign an IRepository<Project, object> to a IRepository<Project, int>, for example. In the following code, the last assignment won't compile:
public interface IContravariant<T, in TKey> where T : class
{
T FindSingle(TKey id);
}
public class objCV : IContravariant<Project, object>
{
public Project FindSingle(object id)
{
return null;
}
public static void test()
{
objCV objcv = new objCV();
IContravariant<Project, Project> projcv;
IContravariant<Project, int> intcv;
projcv = objcv;
intcv = objcv;
}
}
In this article, they are telling us that the type parameter is treated as invariant by the compiler:
Variance applies only to reference types; if you specify a value type
for a variant type parameter, that type parameter is invariant for the
resulting constructed type.
From: http://msdn.microsoft.com/en-us/library/dd799517.aspx
I need some advice/help on this, I can't see the wood from the trees any more.
It's a straight forward series of classes implementing some interfaces using generics.
Then I'm trying to cast the concrete types for example:
MyGenericObject<SomeObject> _obj;
IMyGenericObject<ISomeObject> _genObj = (IMyGenericObject<ISomeObject>)_obj;
// Invalid cast
I've read some articles about covariance and contravariance but not too clear why this wouldn't be possible, or how to get round it?
So, in this example:
public interface IMyObject<in T> where T : IBaseObject
{
T Activity { get; set; }
}
wouldn't work...
....because, you can't get and set the Activity property.
In this example, I needed to do:
public interface IMyObject<out T> where T : IBaseObject
{
T Activity { get; }
}
hope that helps someone, and thanks to all for help!
You can only do that if you declare the interface as having a covariant (out) parameter. You can only do that if the parameter is used covariantly.
For example, if the interface IMyGenericObject<T> has a method taking a T parameter, this prevents you from declaring the parameter as covariant. Conversely, if there is a method that returns a T, that prevents you from declaring the parameter as contravariant.
EDIT
In response to your comment on SLaks's answer, I'm tempted to repeat everything Eric Lippert has ever written on co- and contravariance. See http://blogs.msdn.com/b/ericlippert/archive/tags/Covariance+and+Contravariance/ and also his answers in SO (most recently https://stackoverflow.com/a/8380213/385844)
To summarize:
You can't cast IList<string> to IList<object> because it's legal to pass a FileInfo to an IList<object>, but it is not legal to pass it to an IList<string>.
You can't cast an IList<object> to an IList<string>, because it's legal to retrieve an item from an IList<string> and assign it to a string reference, but an IList<object> might contain a FileInfo, which can't be assigned to a string reference.
EDIT 2
Since you asked for advice, it's also possible to split your interfaces into co- and contravariant parts. To continue with the list example, you could have these interfaces
public interface ICovariantList<out T>
{
T this[int index] { get; }
//...
}
public interface IContravariantList<in T>
{
T this[int index] { set; }
void Add(T item);
//...
}
public class SomeList<T> : ICovariantList<T>, IContravariantList<T>
{
//...
}
This allows you to use the class covariantly or contravariantly, depending on the context.
You need to declare the interface as having a covariant (out) generic parameter.