Manipulate generic objects in parent abstract class (covariance/contravariance)
I'm trying to implement a system that allows to read and interpret lines from a file.
I need to manage different file formats. For that I have an abstract Importer
class that is is inherited and implemented differently (based on the file format).
The lines of a file can result in different object, so I created a generic interface that know how to parse the line, validate it, etc: public interface ILineImporter<ObjType> where ObjType : IImportableObject
The concrete Importer
class knows which LineImporter
to use for a given line, via the overriden abstract method public abstract ILineImporter<IImportableObject> GetLineImporter(string line);
.
The problem is that in the implementation of this method, the returned type depends of the concrete Importer
and line:
public override ILineImporter<IImportableObject> GetLineImporter(string line)
{
// TODO: Return the appropriate LineImporter for the given line
// For the example, I always return a MyObjectALineImporter, but it can potentially be any ILineImporter<IImportableObject>
return new MyObjectALineImporter();
}
That doesn't compile, because the compiler can't implicitly convert MyObjectALineImporter
to ILineImporter<IImportableObject>
.
If I add the in
or out
keywords to use covariance/contravariance, the compiler indicates that my generic interface is not covariantly/contravariantly valid.
Here is the simplified source code that you can throw in a standard Console app to reproduce the issue:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ConsoleApplication2
{
public interface IImportableObject { }
public class MyObjectA : IImportableObject { }
public interface ILineImporter<ObjType> where ObjType : IImportableObject
{
ObjType GetObject();
bool IsObjectValid(ObjType o);
}
/// <summary>
/// Concrete class that knows how to get the appropriate MyObjectA instance, validate it, etc.
/// </summary>
public class MyObjectALineImporter : ILineImporter<MyObjectA>
{
public MyObjectA GetObject()
{
Console.WriteLine("GetObject");
return new MyObjectA(); // For the example, I create a new instance but this method can potentially return an existing object from DB.
}
public bool IsObjectValid(MyObjectA o)
{
Console.WriteLine("IsValid");
// TODO : Test if the object is valid
return true;
}
}
public abstract class Importer
{
public abstract ILineImporter<IImportableObject> GetLineImporter(string line);
public void Importe(string text)
{
using (StringReader reader = new StringReader(text))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var lineImporter = this.GetLineImporter(line);
var obj = lineImporter.GetObject();
bool isValid = lineImporter.IsObjectValid(obj);
}
}
}
}
public class ConcreteImporter1 : Importer
{
public override ILineImporter<IImportableObject> GetLineImporter(string line)
{
// TODO: Return the appropriate LineImporter for the given line
// For the example, I always return a MyObjectALineImporter, but it can potentially be another ILineImporter
return new MyObjectALineImporter();
}
}
class Program
{
static void Main(string[] args)
{
Importer importer = new ConcreteImporter1(); // TODO : Retrieve the appropriate Importer with a Factory.
importer.Importe(string.Empty);
Console.ReadKey();
}
}
}
What would be the right way to handle this problem?
Because the interface do get ObjType
as a parameter (of a method), and returns it (from another method), i cannot be covariant, nor contravariant.
I suggest you create a non-generic ILineImporter
interface, which it's methods works with IImportableObject, which the generic interface would extand/inherit from.
Then MyObjectALineImporter
would be able to be casted to 'ILineImporter` (the non-generic one).
public interface ILineImporter
{
IImportableObject GetObject();
bool IsObjectValid(IImportableObject o);
}
public interface ILineImporter<ObjType> : ILineImporter
where ObjType : IImportableObject
{
ObjType GetObject();
bool IsObjectValid(ObjType o);
}
You could add an invariant interface that Importer
inherits from:
public interface ISomeInterface<TObject> where TObject : IImportableObject {
ILineImporter<TObject> GetLineImporter(string line);
}
public abstract class Importer<T> : ISomeInterface<T> where T: IImportableObject
{
public abstract ILineImporter<T> GetLineImporter(string line);
public void Importe(string text)
{
using (StringReader reader = new StringReader(text))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var lineImporter = this.GetLineImporter(line);
var obj = lineImporter.GetObject();
bool isValid = lineImporter.IsObjectValid(obj);
}
}
}
}
Your ConcreteImporter1
then inherits a type parameterized Importer
:
public class ConcreteImporter1 : Importer<MyObjectA>
{
public override ILineImporter<MyObjectA> GetLineImporter(string line)
{
return new MyObjectALineImporter();
}
}
Note that ISomeInterface
is invariant, ie both co- and contravariant, so there is neither in
nor out
in the definition.
上一篇: 关键字中的c#泛型“