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

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

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


person Dan Bryant    schedule 24.07.2014    source источник
comment
Мы пытались заставить это работать по тем же причинам, но потерпели неудачу.   -  person CSharpie    schedule 24.07.2014
comment
Учитывая, что это основной вариант использования CallerInfo, решение с этим атрибутом просто ужасно.   -  person nicodemus13    schedule 14.09.2014
comment
Все, что вам нужно, - это дополнительный тип (и несколько общих методов). Надеюсь, я дал удовлетворительный ответ на эту проблему.   -  person John Leidegren    schedule 06.11.2014
comment
Для будущего обращения к любому, кто найдет этот вопрос, интерполированные строки прекрасно решают эту проблему, и я настоятельно рекомендую использовать их для различных подходов к хакерским параметрам. Лучшая функция на свете.   -  person Dan Bryant    schedule 24.02.2016


Ответы (5)


Мой предпочтительный способ: Только два символа наверху - хоть и некрасивый язык «взломать»;

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

Альтернатива

Мой коллега подошел к этой работе следующим образом:

public static class DebugHelper

    public static Tuple<string,int> GetCallerInfo(
      [CallerMemberName] string memberName = "", 
      [CallerLineNumber] int lineNumber = 0)
    {
        return Tuple.Create(memberName,lineNumber);
    }
}

Информационный метод:

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);
person CSharpie    schedule 24.07.2014
comment
Это вдохновило меня на подход, который я выбрал в качестве ответа. - person Dan Bryant; 24.07.2014
comment
Мне нравится использовать свойство ThreadContext для пересылки информации о вызывающем абоненте в мой шаблон, как я описал здесь: ссылка - person Ingo; 02.03.2017

Итак, я действительно столкнулся с этой проблемой, но по другой причине. В конце концов я решил это вот так.

Во-первых, разрешение перегрузки в C # (универсальные методы - идеальные кандидаты). Я использовал T4 для создания этих перегрузок методов расширения с поддержкой до 9 аргументов. Вот пример всего с 3 аргументами.

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. У 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);
    }
}

Вуаля!

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

person John Leidegren    schedule 06.11.2014
comment
Очень хитрый маленький трюк для предотвращения проблем с разрешением перегрузки. Определенно понадобятся хорошие комментарии в коде, чтобы избежать путаницы в будущем. :) - person Dan Bryant; 06.11.2014
comment
Что касается комментариев, я оставил комментарий по типу LogFormatWithOptionalParameterList. Надеюсь, кто-нибудь, столкнувшийся с этой ошибкой компилятора, немедленно проверит комментарий к этому типу. Я обновил свой ответ фактическим комментарием. - person John Leidegren; 06.11.2014
comment
Если бы возникла та же проблема, придумал аналогичное решение, необязательный параметр стопора с использованием неустановленного класса. Я тоже сторонник описательных имен, но мое (thisStrangeParamPreventsAccidentalCallsToOverloadsSuchThatCallerInfoGetsSet) определенно нуждается в некоторой обрезке перед выпуском. Кстати. Меня не волнует, что мой неустановленный класс может быть предоставлен как null. Проблема заключается в том, что аргументы информации о вызывающем абоненте не перекачивают аргументы от ничего не подозревающих вызывающих абонентов. И я не забочусь о том, что он изначально не подтвержден, по той же причине. И: Спасибо за this ILogTag трюк - person Evgeniy Berezovsky; 16.04.2015

Основываясь на ответах, предоставленных другими, я вижу, что они в значительной степени основаны на сначала захвате контекста, а затем вызове метода ведения журнала с захваченным контекстом. Я придумал это:

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

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

person Dan Bryant    schedule 24.07.2014

Если вы сделаете параметры формата необязательными в своем "Уродливом решении", вам не потребуется особая перегрузка для каждого количества параметров, но достаточно одного для всех! например:

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

тогда вы можете вызвать его с тремя параметрами, т.е.

Info("No params");
Info("One param{0}",1);
Info("Two param {0}-{1}",1,2);

Вы можете легко минимизировать риск случайного заполнения CallerMemberName и CallerLineNumber, добавив гораздо больше необязательных аргументов форматирования, чем вам когда-либо понадобится, например arg0, ... arg20.

или вы можете объединить его с решением Джона Лейдегрена, то есть добавить параметр guarging .... между argsX и последними двумя параметрами ...

person Bohdan    schedule 19.08.2016

Способ 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"));
}
person AndreyAkinshin    schedule 24.07.2014
comment
Имейте в виду, что это снижает производительность, поскольку создание стека вызовов намного дороже по сравнению с постоянными значениями этих причудливых атрибутов. - person CSharpie; 24.07.2014
comment
@CSharpie, я добавил способ 2. - person AndreyAkinshin; 24.07.2014
comment
Также: эта проблема предоставляет бесполезную информацию в асинхронных методах. - person NineBerry; 05.07.2018