代码之家  ›  专栏  ›  技术社区  ›  psubsee2003 Miki Shah

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

  •  2
  • psubsee2003 Miki Shah  · 技术社区  · 16 年前

    我对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 回复  |  直到 16 年前
        1
  •  8
  •   Filip Ekberg    16 年前

    对象定向我的朋友。从一个VB6的角度来看,这可能不是你所习惯的。但是,与其有file1->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关键字时, 调用类成员而不是 已经 已更换。那些基层成员是 称为隐藏成员。隐藏类 如果 派生类的实例被强制转换 到基类的实例。

    如果您想使用虚拟方法:

    对于派生的 全班接管一个班 基类的成员,即 类必须将该成员声明为 事实上的。这是由 将虚拟关键字添加到 成员的返回类型。派生的 然后类可以选择使用 重写关键字,而不是新建到 替换基类实现 有自己的。

    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。

    如何将此应用于示例

    假设您有一个loadcv方法,而不是有5个返回每个对象的不同方法,您可以很容易地说:“嘿,我将返回一个cvsfile,您不在乎其他任何东西吗?”.

    那里有很多好的旋转木马,“儿童节目”有关于基本原理的最好的插图。看看这个: Object Orientation For Kids (不冒犯)

        2
  •  4
  •   Kevin    16 年前

    有一些很好的答案可以让大家 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"]);
    
        3
  •  2
  •   Paul Sasik    16 年前

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

    但是,函数重载有什么问题?似乎非常适合这种情况。不要忘记,您可以拥有5个公共重载函数,这些函数可以委托给私有函数,从而以您希望的任何方式实现您的目标。

        4
  •  2
  •   Eric Lippert    16 年前

    可以 做这个,但是你 不应该 :

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

    泛型的要点是编写代码 完全通用的 .readfile的实现只能处理类型file1到file5,因此不能 通用的 .

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

    但也许更好的方法还是“什么都不做”。您现在有了工作代码。我知道现在想做工作以避免将来的维护成本,但是你必须权衡那些潜在的成本(可能永远无法实现)和建造一些高度通用的东西的成本,因为你可能不需要,或者更糟的是,在将来可能不会真正做到你想要的。我说坚持你所知道的有效。

        5
  •  1
  •   Rubens Farias    16 年前

    你必须做到 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
    }
    
        6
  •  1
  •   Matthew Whited    16 年前

    我有点无聊…下面是一个使用泛型、扩展方法和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>();
        }
    }
    
        7
  •  1
  •   Tigran    16 年前

    使用虚拟或抽象函数。

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

    总是在基类的虚函数上操作,从不处理 直接实施。 祝你好运。

        8
  •  1
  •   Jacobs Data Solutions    16 年前

    使用通用方法:

    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”子句中转换为一些基类或接口。

    编辑:修改了我的代码,让我说的更清楚一点。