2016-12-13 5 views
1

Я продолжаю писать несколько функций, многократно используемых в C#. Многие функции возвращают несколько значений, для которых я использую или ref параметры. Многие функции также содержат дополнительную информацию, которая может быть полезной для некоторых абонентов (но не для всех абонентов).Шаблон дизайна или кода для возврата нескольких необязательных значений из функции

Например, функция чтения CSV-файла может иметь дополнительную информацию, например no. пустых строк, №. строк с повторяющимися значениями и некоторых других статистических данных.

Дополнительная информация также может включать в себя предупреждение, сообщения и т.д.

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

Я просто хотел, чтобы выяснить, есть ли способ сделать дополнительную информацию доступной для абонентов, так что абонент может выбрать или получить некоторые из необязательной дополнительной информации, которую он заинтересован в.

Например , Func 1 может вызывать Func B. После его вызова он получает все стандартные значения возврата. Кроме того, может ли он вызвать что-то вроде FuncB.GetAdditionalInfo (infoType), если Func B не будет выполнен снова?

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

Одна из возможностей Func B заключается в том, чтобы хранить все это в каких-либо глобальных переменных, которые вызывающий может получить при необходимости. Но если у класса утилиты есть несколько таких resuable функций, мне нужно будет иметь так много общедоступных переменных, для дополнительной информации о каждой функции!

Я нахожусь на .NET 4.5 на данный момент. Есть ли шаблон дизайна для этого? Я открыт, чтобы узнать, есть ли хорошее решение в F #.

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

+1

Вы можете хранить данные в некоторых концепциях метаданных. Это просто словарь с ключом. – Michael

+0

Ya, но, если возможно, я хочу, чтобы каждый раз не вводить значение в вызывающем абоненте. – AllSolutions

+0

И как мне сделать эту метаданную опциональной в вызывающем, если она не заинтересована в этом? – AllSolutions

ответ

2

Я не претендую на то, чтобы представить вам идеальную реализацию, но вот тот, который имеет смысл для меня. Создайте две разные структуры данных: один будет представлять варианты, которые ваша функция принимает, а вторая - та, которую возвращает ваша функция. Например:

public class Helper 
{ 

    // General cover-it-all implementation that accepts an option object 
    // and analyzes based on the flags that are set in it 
    public static CSVStatistics AnalyzeCSV(string file, CSVAnalysisOptions options) 
    { 
     // define what we are analysing by reading it from the 
     // from the options object and do your magic here 
    } 

    // Specific implementation that counts only blank lines 
    public static long CountBlankLines(string file) 
    { 
     var analysisResult = AnalyseCSV(file, new CSVAnalysisOptions 
     { 
      IsCountingBlanks = true 
     }); 
     //I'm not doing a null check here, because I'm settings the 
     //flag to True and therefore I expect there to be a value 
     return analysisResult.BlanksCount.Value; 
    } 
} 
// Analysis options structure 
public struct CSVAnalysisOptions 
{ 
    public bool IsCountingBlanks { get; set; } 
    public bool IsCountingDuplicates { get; set; } 
    public bool IsCountingOther { get; set; } 
} 

// Analysis results structure 
public struct CSVStatistics 
{ 
    public long TotalLineCount { get; set; } 
    public long? BlanksCount { get; set; } 
    public long? DuplicatesCount { get; set; } 

} 

В приведенном выше примере CountBlankLines является конкретной реализацией, которая рассчитывает только пустые строки и действуют как «сахар», что упрощает вызов, в то время как AnalyzeCSV метод, который на самом деле будет делать подсчет. Также обратите внимание на то, как структура CSStatistics имеет значение NULL long. Это позволит вам проверить, является ли значение нулевым и, следовательно, известно, что он фактически не анализировался вместо вывода нуля (что является возможным значением).

Структура CSVAnalysisOptions также может быть заменена битовыми флагами, вы можете прочитать о них здесь - https://www.dotnetperls.com/enum-flags.

2

Я чувствую, что вы пытаетесь построить очень chunky API, который может делать много вещей одним выстрелом. Вообще говоря, нам не нравятся коварные API, потому что они могут усложниться, особенно если есть побочные эффекты или необычные причуды во взаимодействиях между опциями в API.

Честно говоря, лучший способ сделать это - создать API-интерфейс chattier, в котором каждый вызов делает одну вещь, делает это правильно и делает это хорошо.

Когда вы это сделаете, код становится легче измерять и тестировать единицы измерения.

Это не означает, что нет причин для умеренного количества неуверенности, но это должно быть логично.

Так, например, если вы разорвали файл изображения для декодирования, скажем, PNG или JPG, вам понадобится ширина изображения, высота, разрешение и тип цвета спереди. Было бы разумно схватить всех из них за один раз. Вам нужно будет сразу же выдать информацию о метаданных или цветовой профиль? Возможно нет.

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

«Но исполнение!» вы говорите: «Как насчет производительности ?!»

Простой. Вы измеряете это и видите, что выпадает. Несколько лет назад написал PNG-декодер, и в отличие от libpng, который читает фрагменты последовательно, я думал, что будет проще просто создать базу данных впереди, которая отображает, где находится каждый фрагмент файла, а затем ссылается на эту базу данных, чтобы найти какой-либо данный фрагмент , Удивительно, но это существенно повлияло на производительность и сделало потребительский код намного легче читать и поддерживать.

Позволяет вещам вызываться несколько раз, и если есть проблема с производительностью, выясните, как его решить. Как правило, вы делаете это с кешем или сеансом, приватным для объектов, которые получают информацию.

То, что вы описываете, звучит так, будто было бы легко читать и поддерживать, не говоря уже о проверке.

+0

Каждая функция полезности не делает целого много вещей. Он выполняет свою целевую работу, и по пути он подбирает некоторые метаданные, которые я просто хочу передать вызывающему. Это оно! – AllSolutions

+0

Можно сказать, что я пытаюсь получить доступ к некоторым локальным переменным функции вызываемого абонента из функции вызывающего абонента после выполнения функции вызываемого абонента, которая не поддерживается непосредственно языком; следовательно, пытаясь найти, есть ли способ, кроме использования, ref, Tuple, Dictionary и т. д. – AllSolutions

+0

Доступ к метаданным осуществляется отдельно. Если это необходимо для потока вызывающего кода, то это не метаданные. Если это важно, сверните его в соответствующий объект и верните его. Если это не важно, сделайте отдельные аксессоры. – plinth

0

Вот шаблон F #, который может быть подходящим для вашего прецедента. Это почти та же самая модель, которую библиотека Argu использует для аргументов командной строки: вы объявляете discriminated union, который содержит все возможные «флаги», которые ваша функция может захотеть вернуть (я помещаю «флаги» в кавычки, потому что некоторые из них могут больше, чем просто booleans), а затем ваша функция может вернуть список этих значений. Если есть десятки, то набор может стоить того (потому что список нужно искать линейно), но если вы не ожидаете вернуть более семи или восьми таких флагов, то дополнительная сложность набора не будет стоит того, чтобы вы могли использовать список.

Некоторые F # код, иллюстрирующий, как можно использовать этот шаблон, с фиктивными функциями, где ваш бизнес логика будет идти:

type Notifications 
    | InputWasEmpty 
    | OutputWasEmpty 
    | NumberOfBlankLinesInOutput of int 
    | NumberOfDuplicateLinesInOutput of int 
    | NumberOfIgnoredErrors of int 
    // Whatever else... 

type ResultWithNotifications<'a> = 'a * Notifications list 
// The syntax "TypeA * TypeB" is F# syntax for Tuple<TypeA,TypeB> 
// And the 'a is F# syntax for a generic type 

type outputRecord = // ... insert your own data type here 

// Returns the filename of the output file, plus various notifications 
// that the caller can take action on if they want to 
let doSomeProcessing data : ResultWithNotifications<outputRecord list> 
    let mutable notifications = [] 
    let outputFileName = getSafeOutputFilename() 
    if List.isEmpty data then 
     notifications <- InputWasEmpty :: notifications 
    let output = data |> List.choose (fun record -> 
     try 
      let result = record |> createOutputRecordFromInputRecord 
      Some result 
     except e 
      eprintfn "Got exception processing %A: %s" record (e.ToString()) 
      None 
    ) 
    if List.isEmpty output then 
     notifications <- OutputWasEmpty :: notifications 
    if List.length output < List.length data then 
     let skippedRecords = List.length data - List.length output 
     notifications <- (NumberOfIgnoredErrors skippedRecords) :: notifications 
    // And so on. Eventually... 
    output |> writeOutputToFilename outputFileName 
    outputFileName, notifications // Function result 

Будем надеяться, что F # код понятен без объяснения причин, но если есть что-то, что не ясно, в приведенном выше, дайте мне знать, и я попытаюсь объяснить.