簡體   English   中英

創建一個可以在C#中處理多個返回類型的函數

[英]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如何在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關鍵字時,將調用新的類成員而不是已替換的基類成員。 這些基類成員稱為隱藏成員。 如果將派生類的實例強制轉換為基類的實例,則仍可以調用隱藏類成員。

如果您想使用虛擬方法:

為了使派生類的實例完全從基類接管類成員,基類必須將該成員聲明為虛擬成員。 這是通過在成員的返回類型之前添加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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM