【问题标题】:Generics and supertypes泛型和超类型
【发布时间】:2012-09-12 14:05:17
【问题描述】:

在我当前的项目中,我们解析从外部供应商处收到的 CSV 文件。 但是,由于供应商将来会支持 XML 文件,如果管理层决定我们应该使用 XML 格式,我想提供一种简单的方法来更改我们的代码。

为此,我们的“工人”类应该只引用数据类,而不知道源是 CSV 还是 XML 文件。 但是,我们确实有一些工具(用于调试和测试)是专门为一个源文件(目前是 CSV)编写的。

可能描述有点不清楚,但我希望下面的示例能够为您提供足够的信息来帮助我。 已经从类中剥离了不相关的功能,类/接口已被重命名,并且为了示例的目的,整体解决方案已被大大简化。 目前,我有以下设置。

数据“基”类(可以是任何东西): 这些类由(一个)解析器返回。他们真正做的唯一一件事就是包含数据(可以被视为一种 DTO)。

public class Person
{
    public string FirstName { ... };
    public string LastName { ... };
    public int Age { ... };

    public Person()
    {
    }
}

ICsvObject 接口: CSV 数据对象的接口。这里最重要的是LoadFromCsv方法,因为它会被CsvParser类使用

public interface ICsvObject
{
    int CsvLineNumber { get; set; }
    void LoadFromCsv(IList<string> columns);
}

CSV 数据类: 这些通常继承自数据类并实现ICsvObject 接口

public class CsvPerson : Person
{
    public int CsvLineNumber { get; set; }
    public void LoadFromCsv(IList<string> columns)
    {
        if (columns.count != 3)
            throw new Exception("...");

        this.FirstName = columns[0];
        this.LastName = columns[1];
        this.Age = Convert.ToInt32(columns[2]);
    }
}

IParser 接口: 该接口为其他类提供了一种在不知道源文件类型的情况下引用解析器的方法。

public interface IParser<T> where T : new()
{
    IList<T> ReadFile(string path);
    IList<T> ReadStream(Stream sSource);
    IList<T> ReadString(string source);
}

CsvParser 类: 此类实现IParser 接口并提供解析CSV 文件的方法。 将来,可能会决定为 XML 文件提供解析器。

public class CsvParser<CsvObjectType> : IParser<CsvObjectType> where CsvObjectType : new(), ICsvObject
{
    public IgnoreBlankLines { get; set; }

    public ReadFile(string path)
    {
        ...
    }

    public ReadStream(string path)
    {
        ...
    }

    public ReadString(string path)
    {
        List<CsvObjectType> result = new ...;
        For each line in the string
        {
            // Code to get the columns from the current CSV line
            ...

            CsvObjectType item = new CsvObjectType();
            item.CsvLineNumber = currentlinenumber;
            item.LoadFromCsv(columns);
            result.add(item);
        }
        return result;
    }
}

现在我已经解释了一些情况,让我们来解决问题: 'Worker' 类不应该为我们使用的解析器类型而烦恼。 他们应该从解析器接收到的只是一个数据对象列表(例如 Person),他们不需要ICsvObject 接口提供的额外信息(在这个例子中是CsvLineNumber 以及其他真实的东西)情况)。 但是,其他工具应该能够获取额外信息(调试/测试程序...)。

所以,我真正想要的是以下内容:

ParserFactory 类: 此类返回特定数据类型的正确解析器。 将来切换到 XML 时,必须创建 XML 解析器并更改工厂类。 所有其他调用工厂方法的类都应该收到一个有效的IParser 类,而不是一个特定的解析器。

public class ParserFactory
{
    //Instance property
    ...

    public IParser<Person> CreatePersonParser()
    {
        return new CsvParser<CsvPerson>();
    }
}

这样做,工作类将调用工厂方法,无论我们使用什么类型的解析器。 之后可以调用ParseFile 方法以提供“基本”数据类的列表。 返回一个 Csv 解析器是可以的(它实现了 IParser 接口)。 但是不支持泛型类型。 返回CsvParser&lt;Person&gt; 对工厂有效,但Person 类没有实现ICsvObject 接口,由于泛型约束,不能与CsvParser 一起使用。

返回 CsvParser 类或 IParser 将要求调用类知道我们正在使用哪个解析器,因此这不是一个选项。 使用两个泛型类型输入(一个用于 CsvObject 类型,一个用于返回类型)创建 CsvParser 类也不起作用,因为其他工具应该能够访问 ICsvObject 接口提供的额外信息。

也值得一提。这是一个正在修改的旧项目。它仍然是 .NET 2.0。 但是,在回答时,您可以使用更新的技术(如扩展方法或 LINQ 方法)。以 .NET 2.0 和更新的方式回答这个问题会让你获得更多的荣誉 :-)

谢谢!

【问题讨论】:

  • 如果您找到一种方法来总结您的核心问题并在顶部表示它,您可能会更好地参与这个非常长的问题。
  • 一整堵文字长城,没有明确的问题。你问的是什么?
  • 实际问题:我怎样才能返回一个返回基本数据类(Person,而不是 CsvPerson)的 IParser 类,同时仍然保持在创建返回特定数据类的 CsvParser 实例的其他情况下的能力( CsvPerson,而不是人)。
  • 通常你最好将问题限制在一个展示问题的简化示例中......
  • 该死,得走了,但我会在今晚晚些时候尝试找到一个代表不同要求的简化示例。提前致谢!

标签: c# .net generics superclass


【解决方案1】:

我认为你让它变得比必要的复杂。

为什么不

public interface IParser
{
    // this one should be enough as file and string can be accessed via Stream
    IList<Person> ReadStream(Stream sSource); 
    IList<Person> ReadFile(string path);
    IList<Person> ReadString(string source);
}

那么你有

public class CsvParser : IParser { ... }
public class XmlParser : IParser { ... }

我认为不需要 CsvPerson/XmlPerson。每个解析器实现只构建普通人,例如:

public class CsvParser : IParser
{
    public IList<Person> ReadString(string path)
    {
        List<Person> result = new ...;
        For each line in the string
        {
            // Code to get the columns from the current CSV line
            Person p = new Person();
            p.Name = columns[0];
            p.Age = columns[1].AsInt();
            result.add(item);
        }
        return result;
    }
}

【讨论】:

  • 问题是,这不仅仅是关于人。 Person CSV 文件包含三列(第一列是名字,第二列是姓氏,第三列是年龄)。但是例如,对于 Company CSV 文件,我们将只有 2 列......所以应该以不同的方式解析它......因此 LoadFromCsv 方法。另外:就像(我知道 :-))长描述中提到的那样,有时,我们确实需要 CsvPerson 对象包含的额外信息。
【解决方案2】:

感谢您的调查。

我设法通过创建工作人员类使用的代理类找到了解决方案:

public class CsvParserProxy<CsvObjectType, ResultObjectType> : IParser<ResultObjectType> where CsvObjectType : new(), ResultObjectType, ICsvObject where ResultObjectType : new()
{
    private object _lock;
    private CsvParser<CsvObjectType> _CsvParserInstance;
    public CsvParser<CsvObjectType> CsvParserInstance {
        get {
            if (this._CsvParserInstance == null) {
                lock ((this._lock)) {
                    if (this._CsvParserInstance == null) {
                        this._CsvParserInstance = new CsvParser<CsvObjectType>();
                    }
                }
            }

            return _CsvParserInstance;
        }
    }

    public IList<ResultObjectType> ReadFile(string path)
    {
        return this.Convert(this.CsvParserInstance.ReadFile(path));
    }

    public IList<ResultObjectType> ReadStream(System.IO.Stream sSource)
    {
        return this.Convert(this.CsvParserInstance.ReadStream(sSource));
    }

    public IList<ResultObjectType> ReadString(string source)
    {
        return this.Convert(this.CsvParserInstance.ReadString(source));
    }

    private List<ResultObjectType> Convert(IList<CsvObjectType> TempResult)
    {
        List<ResultObjectType> Result = new List<ResultObjectType>();
        foreach (CsvObjectType item in TempResult) {
            Result.Add(item);
        }

        return Result;
    }
}

然后工厂类创建返回基本数据对象的 CsvParserProxies。 其他人如果想从 CsvObjects 中获取额外信息,可以直接创建 CsvParser 类。

【讨论】:

    猜你喜欢
    • 2015-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多