Создание одной функции, которая может обрабатывать несколько типов возврата в C # - PullRequest
2 голосов
/ 14 января 2010

Я относительно новичок в C #, выполнив большую часть моего предыдущего программирования на VB6. Я знаю, что C # гораздо более явно напечатан, чем VB, но я надеюсь, что есть решение для моей проблемы.

Я работаю над проектом, который предназначен для открытия, анализа, проверки и, в конечном итоге, редактирования 5 различных файлов CSV, которые используются в качестве входных данных для приложения, которое мы используем. Манипулирование файлами CSV вручную - это то, что делается сейчас, но для большинства пользователей это затруднительно из-за отсутствия документации и поддержки со стороны первоначального разработчика. Моя цель - создать графический интерфейс, который позволит пользователям напрямую редактировать поля и создавать новый файл 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 ]

8 голосов
/ 14 января 2010

Объектная ориентация моего друга. Исходя из перспективы 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.

Редактировать

Просто немного прояснить

Когда используется новое ключевое слово, новое члены класса называются вместо члены базового класса, которые были заменены. Эти члены базового класса называется скрытых членов. Скрытый класс Члены могут быть вызваны, если экземпляр производного класса приведен к экземпляру базового класса.

А если вы хотите использовать вместо этого виртуальные методы:

Для экземпляра производного класс, чтобы полностью взять на себя класс член из базового класса, базовый класс должен объявить этот член как виртуальная. Это достигается добавив виртуальное ключевое слово перед тип возвращаемого члена. Производное Затем класс имеет возможность использования переопределить ключевое слово вместо нового, чтобы заменить реализацию базового класса со своим.

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, вас больше ничего не волнует!».

Есть много хороших туориалов, и «Программирование для детей» имеет лучшие иллюстрации по основам. Проверьте это: Ориентация на объект для детей (Без обид.)

4 голосов
/ 14 января 2010

Есть несколько хороших ответов о создании общего класса 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 голосов
/ 14 января 2010

Вы можете сделать это, но вы не должны :

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

Суть обобщений заключается в написании кода, полностью обобщенного . Реализация ReadFile сможет обрабатывать только типы File1-File5, и поэтому не является generic .

Лучший подход, как уже говорили другие: найти абстракцию, лежащую в основе всех форматов файлов, и создать иерархию классов, которая представляет эту абстракцию.

Но, возможно, лучший подход - это "ничего не делать". У вас есть рабочий код сейчас. Я понимаю, что хочу выполнять работу сейчас, чтобы избежать затрат на техническое обслуживание в будущем, но вы должны сопоставить эти потенциальные затраты (которые никогда не могут быть реализованы) со стоимостью создания чего-то очень общего сейчас, когда вам, возможно, не понадобится, или, что еще хуже, возможно, нет на самом деле делать то, что вы хотите в будущем. Я говорю, придерживайтесь того, что вы знаете, работает.

2 голосов
/ 14 января 2010

Почему бы просто не использовать объект (базу для всех типов .NET) для вашего "неизвестного" типа? Затем вы можете проверить / разыграть его, как вам нужно ...

Но что не так с перегрузками функций? Кажется, идеально подходит для этого сценария. Не забывайте, что вы можете иметь 5 публичных перегруженных функций, которые затем могут делегироваться частным функциям для достижения ваших целей любым желаемым способом.

1 голос
/ 14 января 2010

Мне было немного скучно ... вот пример использования обобщений, методов расширения и 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>();
    }
}
1 голос
/ 14 января 2010

Используйте виртуальные или абстрактные функции.

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

И всегда работать с виртуальной функцией Базового класса и никогда не иметь дело с реализация напрямую. Удачи.

1 голос
/ 14 января 2010

Использование общих методов:

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" выше.

РЕДАКТИРОВАТЬ: изменил мой код, чтобы сделать то, что я говорю немного яснее.

1 голос
/ 14 января 2010

Вы должны сделать эти 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
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...