我对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
}
答案 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>();
}
}