Как использовать ключевое слово params вместе с информацией о вызывающем абоненте в C#?
Я пытаюсь объединить информацию о вызывающем абоненте C# 5.0 вместе с ключевым словом c# params. Намерение состоит в том, чтобы создать оболочку для структуры ведения журнала, и мы хотим, чтобы регистратор форматировал текст как строку.Формат. В предыдущих версиях метод выглядел следующим образом:
void Log(
string message,
params object[] messageArgs = null);
и мы называем это так:
log.Log("{0}: I canna do it cap'n, the engines can't handle warp {1}!",
"Scotty", warpFactor);
теперь мы хотим захватить информацию о вызывающем абоненте и зарегистрировать это. Так подпись становится:
void Log(
string message,
params object[] messageArgs,
[CallerMemberName] string sourceMemberName = null);
Это не компиляции потому что параметры должны быть последним параметром. Поэтому я пробую это:
void Log(
string message,
[CallerMemberName] string sourceMemberName = null,
params object[] messageArgs);
есть ли способ вызвать это без предоставления sourceMembername или явного назначения аргумента messageArgs в качестве именованного параметра? Это побеждает цель ключевого слова params:
// params is defeated:
log.Log("message",
messageArgs: new object[] { "Scotty", warpFactor });
// CallerMemberName is defeated:
log.Log("message", null,
"Scotty", warpFactor);
есть ли способ сделать это? Похоже, что "хакерский" способ передачи информации о вызывающем абоненте исключает использование ключевого слова params. Было бы здорово, если бы компилятор C# признал, что параметры информации вызывающего абонента не действительно параметры на всех. Я не вижу необходимости передавать их в явном виде.
моя резервная копия будет пропускать ключевое слово params, и вызывающий должен будет использовать длинную подпись в этом последнем примере.
4 ответов
Я не думаю, что это можно сделать именно так, как вы хотите это сделать. Тем не менее, я мог бы придумать несколько жизнеспособных обходных путей, которые, вероятно, дадут вам почти те же преимущества.
-
используйте промежуточный вызов метода для захвата имени вызывающего элемента. Первый вызов метода возвращает делегат, который, в свою очередь, может быть вызван для предоставления дополнительных параметров. Это выглядит странно, но это должно работать:
log.Log()("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", warpFactor);
один недостаток здесь в том, что можно позвонить
log.Log("something")
, ожидая, что ваше сообщение будет зарегистрировано, и ничего не произойдет. Если вы используете Resharper, вы можете смягчить это, добавив доLog()
метод, поэтому вы получаете предупреждение, если кто-то ничего не делает с результирующим объектом. Вы также можете немного изменить этот подход, сказав:var log = logFactory.GetLog(); // <--injects method name. log("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", warpFactor);
-
создайте свои сообщения журнала с lambdas, и пусть строка.Формат позаботьтесь о params массив:
log.Log(() => string.Format("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", warpFactor));
это подход, который я обычно использую, и она имеет некоторые побочные преимущества:
- ваш метод журнала может перехватывать исключения, созданные при создании строки отладки, поэтому вместо взлома вашей системы вы просто получаете сообщение об ошибке: "не удалось создать сообщение журнала: [сведения об исключении]".
-
иногда объект, который вы передаете в строку формата, может потребовать дополнительных затрат, которые вы хотите понести только тогда, когда вам нужно это:
log.Info(() => string.Format("{0}: I canna do it cap'n, the engines can't handle warp {1}!", _db.GetCurrentUsername(), warpFactor));
вы бы предпочли, чтобы приведенный выше код не выполнял поездку по базе данных, Если ведение журнала на уровне информации не включено.
в качестве примечания я использую строку.Форматируйте достаточно часто, чтобы я создал вспомогательный метод, чтобы немного сократить синтаксис:
log.Log(() => "{0}: I canna do it cap'n, the engines can't handle warp {1}!" .With("Scotty", warpFactor));
чтобы согласиться с предложением StriplingWarrior вместо делегата, вы могли бы сделать это с беглым синтаксисом.
public static class Logger
{
public static LogFluent Log([CallerMemberName] string sourceMemberName = null)
{
return new LogFluent(sourceMemberName);
}
}
public class LogFluent
{
private string _callerMemeberName;
public LogFluent(string callerMamberName)
{
_callerMemeberName = callerMamberName;
}
public void Message(string message, params object[] messageArgs)
{
}
}
тогда назовите это как
Logger.Log().Message("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", 10);
Logger не должен быть статическим, но это был простой способ продемонстрировать концепцию
Ну, позвольте мне упомянуть один вариант, вы можете использовать отражение, чтобы получить точно такое же имя, как CallMemberName
. Это будет определенно медленнее. Полагаю, если вы не будете регистрировать каждую миллисекунду, этого будет достаточно, чтобы справиться с давлением.
var stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(1).GetMethod().Name;
мне нравится следовать курсу Джима Кристофера (@beefarino) pluralsight для настройки моих собственных проектов ведения журнала. Для этого я клонирую интерфейс ILog как ILogger, я реализую это - скажем, в классе LoggerAdapter - а затем я использую Jim Christophers LogManager, чтобы иметь GetLogger (тип типа)-метод, который возвращает завернутый log4net-logger 'LoggerAdapter':
namespace CommonLogging
{
public class LogManager : ILogManager
{
private static readonly ILogManager _logManager;
static LogManager()
{
log4net.Config.XmlConfigurator.Configure(new FileInfo("log4net.config"));
_logManager = new LogManager();
}
public static ILogger GetLogger<T>()
{
return _logManager.GetLogger(typeof(T));
}
public ILogger GetLogger(Type type)
{
var logger = log4net.LogManager.GetLogger(type);
return new LoggerAdapter(logger);
}
}
}
следующим шагом является создание универсального расширения, подобного этому, установка информации о вызывающем абоненте в ThreadContext.Свойство:
public static class GenericLoggingExtensions
{
public static ILogger Log<TClass>(this TClass klass, [CallerFilePath] string file = "", [CallerMemberName] string member = "", [CallerLineNumber] int line = 0)
where TClass : class
{
ThreadContext.Properties["caller"] = $"[{file}:{line}({member})]";
return LogManager.GetLogger<TClass>();
}
}
имея это на месте, я всегда могу вызвать регистратор перед написанием фактического метода, просто позвонив:
this.Log().ErrorFormat("message {0} {1} {2} {3} {4}", "a", "b", "c", "d", "e");
и если у вас есть conversionPattern вашего PatternLayout настроен на использование свойства:
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%utcdate [%thread] %-5level %logger - %message - %property{caller}%newline%exception" />
</layout>
у вас всегда будет правильный вывод с callerinformation первого .Log () - вызов:
2017-03-01 23:52:06,388 [7] ERROR XPerimentsTest.CommonLoggingTests.CommonLoggingTests - message a b c d e - [C:\git\mine\experiments\XPerimentsTest\CommonLoggingTests\CommonLoggingTests.cs:71(Test_Debug_Overrides)]