Смешивание дополнительных параметров и параметров, когда невозможно просто перегрузить
Подобно этому вопросу, я хочу смешивать необязательные параметры с ключевым словом params, что, конечно, создает двусмысленность. К сожалению, ответ на создание перегрузок не работает, так как я хочу использовать атрибуты информации о вызывающем абоненте, например:
public void Info(string message, [CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0, params object[] args)
{
_log.Info(BuildMessage(message, memberName, lineNumber), args);
}
Создание перегрузки без дополнительных параметров приведет к изменению сайта вызова, не позволяя этим конкретным параметрам работать должным образом.
Я нашел решение, которое почти работает (хотя оно уродливо):
public void Info(string message, object arg0, [CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0)
{
_log.Info(BuildMessage(message, memberName, lineNumber), arg0);
}
public void Info(string message, object arg0, object arg1, [CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0)
{
_log.Info(BuildMessage(message, memberName, lineNumber), arg0, arg1);
}
Проблема заключается в том, что если вы укажете строку для последнего аргумента, разрешение перегрузки предполагает, что вы намерены явно указывать memberName
в перегрузке, которая принимает меньше аргументов, что не является желаемым поведением.
Есть ли способ сделать это (возможно, используя некоторые новые атрибуты, о которых я не узнал?) или мы просто достигли пределов того, что может дать нам поддержка автомагического компилятора?
Ответы
Ответ 1
Мой предпочтительный способ:
Только два чарчатера над головой - уродливый язык "взломать", хотя;
public delegate void WriteDelegate(string message, params object[] args);
public static WriteDelegate Info(
[CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0)
{
return new WriteDelegate ((message,args)=>
{
_log.Info(BuildMessage(message, memberName , lineNumber ), args);
});
}
Использование (укажите свою собственную реализацию BuildMessage
Info()("hello world {0} {1} {2}",1,2,3);
Alternative
Как мой коллега подошел, чтобы сделать эту работу такой:
public static class DebugHelper
public static Tuple<string,int> GetCallerInfo(
[CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0)
{
return Tuple.Create(memberName,lineNumber);
}
}
InfoMethod:
public void Info(Tuple<string,int> info, string message, params object[] args)
{
_log.Info(BuildMessage(message, info.Item1, info.Item2), args);
}
использование:
instance.Info(DebugHelper.GetCallerInfo(),"This is some test {0} {1} {2}",1,2,3);
Ответ 2
Итак, я действительно столкнулся с этой проблемой, но по другой причине. В конце концов я решил это так.
Во-первых, разрешение перегрузки в С# (общие методы - идеальные кандидаты). Я использовал T4 для генерации этих перегрузок метода расширения с поддержкой до 9 аргументов. Вот пример с тремя аргументами.
public static void WriteFormat<T1, T2, T3>(this ILogTag tag, string format, T1 arg0, T2 arg1, T3 arg2
, [CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0
)
{
if (tag != null)
{
var entry = new LogEntry(DateTimeOffset.Now, tag.TagName, new LogString(format, new object[] { arg0, arg1, arg2 }), callerMemberName, System.IO.Path.GetFileName(callerFilePath), callerLineNumber);
tag.Write(entry);
}
}
Что работает отлично, но в итоге приводит к двусмысленности при использовании любой комбинации аргументов, соответствующих списку атрибутов информации о вызывающем абоненте. Чтобы этого не произошло, вам нужен тип, чтобы защитить дополнительный список параметров и отделить его от необязательного списка параметров.
Пустая структура будет очень хорошо (я использую длинные и описательные имена для таких вещей).
/// <summary>
/// The purpose of this type is to act as a guard between
/// the actual parameter list and optional parameter list.
/// If you need to pass this type as an argument you are using
/// the wrong overload.
/// </summary>
public struct LogWithOptionalParameterList
{
// This type has no other purpose.
}
ПРИМЕЧАНИЕ. Я думал об этом абстрактном классе с частным конструктором, но это фактически позволило бы передать null
как тип LogWithOptionalParameterList
. A struct
не имеет этой проблемы.
Вставьте этот тип между фактическим списком параметров и дополнительным списком параметров.
public static void WriteFormat<T1, T2, T3>(this ILogTag tag, string format, T1 arg0, T2 arg1, T3 arg2
, LogWithOptionalParameterList _ = default(LogWithOptionalParameterList)
, [CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0
)
{
if (tag != null)
{
var entry = new LogEntry(DateTimeOffset.Now, tag.TagName, new LogString(format, new object[] { arg0, arg1, arg2 }), callerMemberName, System.IO.Path.GetFileName(callerFilePath), callerLineNumber);
tag.Write(entry);
}
}
Вуаля!
Единственная цель, которую имеет этот тип, состоит в том, чтобы возиться с процедурой разрешения перегрузки, но это также приведет к ошибке компилятора, если вы случайно заполнили значения атрибута информации о вызывающем абоненте (которые должен был предоставить компилятор), когда ваши методы принимают дополнительные Параметры У меня были такие вызовы, которые сразу же привели к ошибкам компилятора.
Ответ 3
На основании полученных ответов, я вижу, что они в основном основаны на первом захвате контекста, а затем на вызове метода ведения журнала с захваченным контекстом. Я придумал это:
public CallerContext Info([CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0)
{
return new CallerContext(_log, LogLevel.Info, memberName, lineNumber);
}
public struct CallerContext
{
private readonly Logger _logger;
private readonly LogLevel _level;
private readonly string _memberName;
private readonly int _lineNumber;
public CallerContext(Logger logger, LogLevel level, string memberName, int lineNumber)
{
_logger = logger;
_level = level;
_memberName = memberName;
_lineNumber = lineNumber;
}
public void Log(string message, params object[] args)
{
_logger.Log(_level, BuildMessage(message, _memberName, _lineNumber), args);
}
private static string BuildMessage(string message, string memberName, int lineNumber)
{
return memberName + ":" + lineNumber + "|" + message;
}
}
Если у вас есть LoggerProxy (метод определения класса Info()
) с именем Log, это примерно так:
Log.Info().Log("My Message: {0}", arg);
Синтаксис кажется мне немного более чистым (дублирующий журнал по-прежнему уродлив, но так оно и есть), и я думаю, что использование структуры для контекста может немного улучшить производительность, хотя мне пришлось бы профилировать уверен.
Ответ 4
Если вы производите параметры формата в своем "Уродливом решении", вам не нужна специальная перегрузка для каждого количества параметров, но для каждого достаточно одного! например:
public void Info(string message, object arg0=null, object arg1=null,
[CallerMemberName] string memberName = "",[CallerLineNumber] int lineNumber = 0)
{
_log.Info(BuildMessage(message, memberName, lineNumber), arg0, arg1);
}
то вы можете вызвать его с тремя параметрами i.e.
Info("No params");
Info("One param{0}",1);
Info("Two param {0}-{1}",1,2);
Вы можете легко минимизировать риск случайного заполнения CallerMemberName и CallerLineNumber, добавляя гораздо больше необязательных аргументов форматирования, чем вам когда-либо понадобится, например. arg0,... arg20.
или вы можете объединить его с решением Джона Лейдегрена, добавив параметр манжеты.... между argsX и двумя последними параметрами...
Ответ 5
Способ 1.
I Вы можете использовать StackFrame
вместо CallerLineNumber
:
public void Info(string message, params object[] args)
{
StackFrame callStack = new StackFrame(1, true);
string memberName = callStack.GetMethod().Name;
int lineNumber = callStack.GetFileLineNumber();
_log.Info(BuildMessage(message, memberName, lineNumber), args);
}
Полезные страницы документации:
Способ 2.
public class InfoMessage
{
public string Message { get; private set; }
public string MemberName { get; private set; }
public int LineNumber { get; private set; }
public InfoMessage(string message,
[CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0)
{
Message = message;
MemberName = memberName;
LineNumber = lineNumber;
}
}
public void Info(InfoMessage infoMessage, params object[] args)
{
_log.Info(BuildMessage(infoMessage), args);
}
public string BuildMessage(InfoMessage infoMessage)
{
return BuildMessage(infoMessage.Message,
infoMessage.MemberName, infoMessage.LineNumber);
}
void Main()
{
Info(new InfoMessage("Hello"));
}