Подтвердить что ты не робот

Смешивание дополнительных параметров и параметров, когда невозможно просто перегрузить

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

Есть ли способ сделать это (возможно, используя некоторые новые атрибуты, о которых я не узнал?) или мы просто достигли пределов того, что может дать нам поддержка автомагического компилятора?

4b9b3361

Ответ 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"));
}