[英]Creating one Function that can handle multiple return types in C#
我對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
}
面向對象我的朋友。 來自VB6的觀點,可能不是您的用途。 但是沒有File1 - > File5,你為什么不擁有一個“CVSFile”對象,如果你真的必須從中得到它。 哪個會在很多方面幫助你。
多態性
來自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關鍵字時,將調用新的類成員而不是已替換的基類成員。 這些基類成員稱為隱藏成員。 如果將派生類的實例強制轉換為基類的實例,則仍可以調用隱藏類成員。
如果您想使用虛擬方法:
為了使派生類的實例完全從基類接管類成員,基類必須將該成員聲明為虛擬成員。 這是通過在成員的返回類型之前添加virtual關鍵字來實現的。 然后,派生類可以選擇使用override關鍵字而不是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
如何在您的示例中應用此功能
假設你有一個LoadCSV方法,而不是有5個不同的方法返回每個對象,你可以輕松地說“嘿我會返回一個CVSFile,你不關心其他任何東西!”。
那里有很多很好的裝飾,而“為孩子們編程”在基礎知識上有最好的插圖。 看看這個: 孩子的面向對象 (沒有冒犯。)
關於創建一個所有文件類型都來自的公共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"]);
為什么不使用對象(所有.NET類型的基礎)作為“未知”類型? 然后你可以根據需要測試/投射它......
但是,函數重載有什么問題? 對於這種情況似乎很完美。 不要忘記,您可以擁有5個公共重載功能,然后可以委托私人功能以任何方式實現您的目標。
你可以做到這一點,但你不應該 :
Dictionary<string, T> ReadFile<T>(string f) {...}
...
Dictionary<string, File1> d = ReadFile<File1>(filename);
泛型的要點是編寫完全通用的代碼。 ReadFile的實現只能處理File1到File5類型,因此不是通用的 。
更好的方法就像其他人所說的那樣:找到所有文件格式的抽象,並創建一個表示抽象的類層次結構。
但也許更好的方法仍然是“什么都不做”。 你現在有工作代碼。 我理解現在想要做的工作是為了避免將來的維護成本,但是你必須權衡那些潛在的成本(可能永遠不會實現)與建造一些你可能不需要的高級通用成本,或者更糟糕的是,可能不會實際上做你想要的將來。 我說堅持你所知道的工作。
您必須使該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
}
使用通用方法:
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”子句中轉換為某些基類或接口。
編輯:改變我的代碼,使我說的更清楚一點。
使用虛函數或抽象函數。
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類的虛函數上運行,從不直接處理實現。 祝好運。
我有點無聊......這里有一個使用泛型,擴展方法和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>();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.