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.

链接地址: http://www.djcxy.com/p/39440.html

上一篇: 关键字中的c#泛型“

下一篇: 操作父级抽象类中的泛型对象(协方差/变换)