【问题标题】:Manipulate generic objects in parent abstract class (covariance/contravariance)操作父抽象类中的通用对象(协变/逆变)
【发布时间】:2014-03-04 09:11:27
【问题描述】:

我正在尝试实现一个允许从文件中读取和解释行的系统。

我需要管理不同的文件格式。为此,我有一个抽象的 Importer 类,它被继承并以不同的方式实现(基于文件格式)。

文件的行可以产生不同的对象,所以我创建了一个通用接口,知道如何解析行,验证它等:public interface ILineImporter<ObjType> where ObjType : IImportableObject

具体的Importer 类通过重写的抽象方法public abstract ILineImporter<IImportableObject> GetLineImporter(string line); 知道将哪个LineImporter 用于给定的行。

问题是在这个方法的实现中,返回的类型取决于具体的Importer和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();
}

这不会编译,因为编译器无法将MyObjectALineImporter 隐式转换为ILineImporter&lt;IImportableObject&gt;

如果我添加 inout 关键字来使用协变/逆变,编译器会指出我的泛型接口不是协变/逆变有效的。

下面是简化的源代码,您可以将其放入标准控制台应用程序中以重现该问题:

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();
        }
    }
}

处理这个问题的正确方法是什么?

【问题讨论】:

    标签: c# generics covariance contravariance


    【解决方案1】:

    因为接口确实将ObjType 作为(方法的)参数并返回(从另一个方法),所以 i 不能是协变的,也不能是逆变的。

    我建议您创建一个非泛型 ILineImporter 接口,它的方法与 IImportableObject 一起使用,泛型接口将从该接口扩展/继承。

    然后MyObjectALineImporter 将能够转换为“ILineImporter”(非泛型)。

    public interface ILineImporter
    {
        IImportableObject GetObject();
        bool IsObjectValid(IImportableObject o);
    }
    
    public interface ILineImporter<ObjType> : ILineImporter
        where ObjType : IImportableObject
    {
        ObjType GetObject();
        bool IsObjectValid(ObjType o);
    }
    

    【讨论】:

    • 现在它说'MyObjectALineImporter.GetObject()' 不能实现'ILineImporter.GetObject()',因为它没有匹配的返回类型'IImportableObject'。这很奇怪,因为它的父类 (ILineImporter) 具有 IImportableObject 的类型约束
    • 您应该对更基本的接口使用显式接口实现。 How to use explicit interface implementation
    • 谢谢,现在可以编译了。但是,它迫使我复制具体 LineImporters 中的每个方法(一个用于非泛型接口,一个用于泛型),并且它迫使我将方法上收到的 IImportableObject 参数转换为具体类(我失去了泛型)。有什么想法吗?
    • 关于方法重复 - 不幸的是,这是一个要求,但只是一个小要求(非通用实现应该只调用通用的。关于导入器的铸造 - 你不会失去优势泛型,因为两件事: 1. 在您的初始实现中,您尝试将行导入器存储为 ILineImporter&lt;IImportableObject&gt;,这与非泛型 ILineImporter 完全相同(这没关系,因为导入器不需要现在正在使用的确切行导入器)。2.在下一条评论中 ->
    • 我简化了我的代码,把它贴在这里,所以实际上我的 LineImporters 中有两个以上的方法。为了避免代码重复和不必要的转换方法,我最终做的是添加一个通用抽象父 LineImporter 类,该类显式实现 2 个接口并调用通用抽象方法。
    【解决方案2】:

    您可以添加Importer 继承自的不变接口:

    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);
               }
           }
       }
    }
    

    然后你的ConcreteImporter1 继承了一个参数化的类型Importer

    public class ConcreteImporter1 : Importer<MyObjectA>
    {
       public override ILineImporter<MyObjectA> GetLineImporter(string line)
       {
           return new MyObjectALineImporter();
       }
    }
    

    注意ISomeInterface不变的,即既是协变又是逆变,所以定义中既没有in也没有out

    【讨论】:

    • 我不能这样做,因为我的 Importer 具体实现可以根据 'line' 参数返回不同的 LineImporter。 Importer 为给定的行返回适当的 LineImporter。
    • @OlivierPayen:在这种情况下,您需要使用非通用的“基本”接口 - I&lt;DerivedType&gt; 不能分配给 I&lt;BaseType&gt;。这会破坏您设计的其他部分吗?
    • 我认为这就是 Shlomi Borovitz 在他的回答中推荐的内容,但我仍然对此有疑问(阅读他回答的评论)。
    猜你喜欢
    • 1970-01-01
    • 2012-10-08
    • 1970-01-01
    • 2011-07-27
    • 2019-02-10
    • 2014-09-15
    • 2011-02-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多