创建一个可以在C#中处理多个返回类型的函数

时间:2010-01-14 14:29:04

标签: c# c#-3.0

我对C#相对较新,在VB6中完成了我之前的大部分编程工作。我知道C#比VB更明确地输入,但我希望有一个解决方案来解决我的问题。

我正在开发一个项目,旨在打开,解析,验证并最终编辑5个不同的CSV文件,这些文件用作我们使用的应用程序的输入。手动操作CSV文件是现在所做的,但由于缺乏原始开发人员的文档和支持,大多数用户都很难。我的目标是构建一个GUI,允许用户直接编辑字段并创建一个新的CSV文件用作导入。这是现在的基本结构:

public class Dataset
{
    public Dictionary<string,File1> file1 = new Dictionary<string,File1>();
    public Dictionary<string,File2> file2 = new Dictionary<string,File2>();
    public Dictionary<string,File3> file3 = new Dictionary<string,File3>();
    public Dictionary<string,File4> file4 = new Dictionary<string,File4>();
    public Dictionary<string,File5> file5 = new Dictionary<string,File5>();

    public void SelectFiles()
    {
        //User specifies which file(s) are to be opened (default is all 5 files)
    }

    public class File1
    {
        public string Field_1 {get ; set}
        public string Field_2 {get ; set}
        .
        .
        .
        public string Field_10 {get ; set}
    }
    public class File2
    {
        public string Field_1 {get ; set}
        public string Field_2 {get ; set}
        .
        .
        .
        public string Field_31 {get ; set}
    }
    public class File3
    {
        public string Field_1 {get ; set}
        public string Field_2 {get ; set}
        .
        .
        .
        public string Field_57 {get ; set}
    }
    public class File4
    {
        public string Field_1 {get ; set}
        public string Field_2 {get ; set}
        .
        .
        .
        public string Field_68 {get ; set}
    }
    public class File5
    {
        public string Field_1 {get ; set}
        public string Field_2 {get ; set}
        .
        .
        .
        public string Field_161 {get ; set}
    }
}

挑战在于将CSV中的数据读入每个词典。现在,这是通过5个不同的功能实现的(实际上一个功能重载了5次)

public Dictionary<string,File1>ReadFile(string file)
{
    //Open and Parse File #1, and store in File1 class and accessed by file1 dictionary
}
public Dictionary<string,File2>ReadFile(string file)
{
    //Open and Parse File #2, and store in File2 class and accessed by file2 dictionary
}
public Dictionary<string,File3>ReadFile(string file)
{
    //Open and Parse File #3, and store in File3 class and accessed by file3 dictionary
}
public Dictionary<string,File4>ReadFile(string file)
{
    //Open and Parse File #4, and store in File4 class and accessed by file4 dictionary
}
public Dictionary<string,File5>ReadFile(string file)
{
    //Open and Parse File #5, and store in File5 class and accessed by file5 dictionary
}

打开和解析CSV文件的代码几乎完全相同,只是字典的类型不同。因此,当我对此函数进行更改时,我必须确保对其他4个函数进行相同的更改,并且我担心它将在未来维护代码更多的问题。有没有可能的方法我可以创建一个没有重载的单一函数,我可以将类型作为参数传递给函数?

public Dictionary<string,[UnknownType]>ReadFile(string file, [UnknownType] typ)
{
    //Open and Parse File and read into class specified by typ
}

8 个答案:

答案 0 :(得分:8)

面向对象我的朋友。来自VB6的观点,可能不是您的用途。但不是没有File1 - &gt; File5,你为什么不拥有一个“CVSFile”对象,如果你真的必须,那么你可以从中获得。哪个会在很多方面帮助你。

<强>多态性

查看MSDN如何使用Polymorphism in C#

来自MSDN的

片段:

public class BaseClass
{
    public void DoWork() { }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

修改

只是为了清楚一点

  

当使用new关键字时,新的   类成员被调用而不是   已经成为的基类成员   更换。那些基类成员是   叫隐藏的成员。隐藏的课程   如果一个成员仍然可以被调用   派生类的实例是强制转换的   到基类的实例。

如果你想改用虚拟方法:

  

为了派生的实例   上课完全接管一堂课   基类成员,基数   class必须将该成员声明为   虚拟。这是通过   在之前添加虚拟关键字   返回成员的类型。派生的   然后类可以选择使用   覆盖关键字而不是新的   替换基类实现   有自己的。

public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}

在这种情况下,结果将是此

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Also calls the new method.

所有示例均来自MSDN

如何将此应用于您的示例

假设你有一个LoadCSV方法,而不是有5个不同的方法返回每个对象,你可以轻松地说“嘿我会返回一个CVSFile,你不关心其他任何东西!”。

那里有很多很好的装饰,而“为孩子们编程”在基础知识上有最好的插图。看看这个:Object Orientation For Kids(没有冒犯。)

答案 1 :(得分:4)

关于创建一个所有文件类型都来自的公共FileBase类,有一些很好的答案。不过,您可以进一步简化事情。

除了Field_X值的数量之外,您拥有的所有文件类都是相同的,那么为什么不将它们全部表示为List<string>?由于您使用整数键存储它们,为什么不使用内置索引的List<T>?然后你的函数看起来像这样:

List<List<string>> Parse(string file)
{
  List<List<string>> result = new List<List<string>>();
  using (TextReader reader = File.OpenText(file))
  {
    string line = reader.ReadLine();
    while (line != null)
    {
      result.Add(new List<string>(line.Split(',')));
      line = reader.ReadLine();
    }
  }
  return result;
}

你曾经说过的地方

Dictionary<int, File1> file1 = Parse("file1.csv");
Console.Write(file1[0].Field_5);

您现在可以使用

List<List<string>> file1 = Parse("file1.csv");
Console.Write(file1[0][5]);

如果字段名称实际上比Field_5更有趣,请让函数返回List<Dictionary<string,string>>,然后使用

List<List<string>> file1 = Parse("file1.csv");
Console.Write(file1[0]["SomeFieldName"]);

答案 2 :(得分:2)

为什么不使用对象(所有.NET类型的基础)作为“未知”类型?然后你可以根据需要测试/投射它......

但是,函数重载有什么问题?对于这种情况似乎很完美。不要忘记,您可以拥有5个公共重载功能,然后可以委托私人功能以任何方式实现您的目标。

答案 3 :(得分:2)

可以这样做,但你不应该

Dictionary<string, T> ReadFile<T>(string f) {...}
...
Dictionary<string, File1> d = ReadFile<File1>(filename);

泛型的要点是编写完全通用的代码。 ReadFile的实现只能处理File1到File5类型,因此不是泛型

更好的方法就像其他人所说的那样:找到所有文件格式的抽象,并创建一个表示抽象的类层次结构。

但也许更好的方法仍然是“什么都不做”。你现在有工作代码。我理解现在想要做的工作是为了避免将来的维护成本,但是你必须权衡那些潜在的成本(可能永远不会实现)与建造一些你可能不需要的高级通用成本,或者更糟糕的是,可能不会实际上做你想要的将来。我说坚持你所知道的工作。

答案 4 :(得分:1)

您必须使File_类继承基类并将其返回:

public abstract classFileBase
{
    // include common features here
}

public class File1 : FileBase
{
    public string Field_1 {get ; set}
    public string Field_2 {get ; set}
    .
    .
    .
    public string Field_10 {get ; set}
}

public Dictionary<string, FileBase>ReadFile(string file)
{
    //Open and Parse File and read into class specified by typ
}

答案 5 :(得分:1)

使用通用方法:

public abstract class FileBase
{
    public virtual void DoSomeParsing()
    {
    }
}

public class File1 : FileBase
{
}

public class Test
{
    public Dictionary<string, T> ReadFile<T>(string file) where T : FileBase, new()
    {
        Dictionary<string, T> myDictionary = new Dictionary<string, T>();
        myDictionary.Add(file, new T());
        myDictionary[file].DoSomeParsing();
        return myDictionary;
    }

    public object Testit()
    {
        Test test = new Test();
        return test.ReadFile<File1>("C:\file.txt");
    }
}

当然,这并没有解决继承问题,你仍然需要在上面的“as”子句中强制转换为某些基类或接口。

编辑:更改了我的代码,使我说得更清楚。

答案 6 :(得分:1)

使用虚拟或抽象功能。

class Base {
  virtual ReadFile();
}

class File1: Base {
    override ReadFile(); //Reads File1
}


class File2: Base {
    override ReadFile(); //Reads File2
}

...

并且在创建时,让我们说你有一个

的列表
List<Base> baseList
if(I need to read file File1)
   baseList.Add(new File1());

始终在Base类的虚函数上运行,从不处理 直接实施。 祝你好运。

答案 7 :(得分:1)

我有点无聊......这里有一个使用泛型,扩展方法和LINQ的例子......欢迎来到.Net 3.5 :)

public interface IParser
{
    object Parse(string input);
}
public interface IParser<T> : IParser
{
    new T Parse(string input);
}
public class MyParser : IParser<MyObject>
{
    #region IParser<MyObject> Members

    public MyObject Parse(string input)
    {
        if (string.IsNullOrEmpty(input))
            throw new ArgumentNullException("input");
        if (input.Length < 3)
            throw new ArgumentOutOfRangeException("input too short");

        return new MyObject()
        {
            Field1 = input.Substring(0, 1),
            Field2 = input.Substring(1, 1),
            Field3 = input.Substring(2, 1)
        };
    }

    object IParser.Parse(string input)
    {
        return this.Parse(input);
    }

    #endregion
}
public class MyObject
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
    public string Field3 { get; set; }
}
public static class ToolKit
{
    public static Dictionary<string, TResult> ReadFile<TParser, TResult>(
            this string fileName)
        where TParser : IParser<TResult>, new()
        where TResult : class
    {
        return fileName.AsLines()
                       .ReadFile<TParser, TResult>();
    }

    public static Dictionary<string, TResult> ReadFile<TParser, TResult>(
            this IEnumerable<string> input)
        where TParser : IParser<TResult>, new()
        where TResult : class
    {
        var parser = new TParser();

        var ret = input.ToDictionary(
                        line => line, //key
                        line => parser.Parse(line)); //value
        return ret;
    }

    public static IEnumerable<string> AsLines(this string fileName)
    {
        using (var reader = new StreamReader(fileName))
            while (!reader.EndOfStream)
                yield return reader.ReadLine();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var result = new[] { "123", "456", "789" }
            .ReadFile<MyParser, MyObject>();

        var otherResult = "filename.txt".ReadFile<MyParser, MyObject>();
    }
}