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?