Поиск имени переменной, переданной функции
Позвольте мне использовать следующий пример, чтобы объяснить мой вопрос:
public string ExampleFunction(string Variable) {
return something;
}
string WhatIsMyName = "Hello World"';
string Hello = ExampleFunction(WhatIsMyName);
Когда я передаю переменную "WhatIsMyName" в пример функции, я хочу иметь возможность получить строку с исходным именем переменной. Возможно что-то вроде:
Variable.OriginalName.ToString()
Есть какой-либо способ сделать это?
23 ответа
** Нет. ** Я так не думаю.
Имя переменной, которое вы используете, для вашего удобства и читабельности. Компилятору это не нужно и просто выкидывает его, если я не ошибаюсь.
Если это поможет, вы можете определить новый класс с именем NamedParameter с атрибутами Name и Param. Затем вы передаете этот объект в качестве параметров.
То, что вы хотите, не возможно напрямую, но вы можете использовать выражения в C# 3.0:
public void ExampleFunction(Expression<Func<string, string>> f) {
Console.WriteLine((f.Body as MemberExpression).Member.Name);
}
ExampleFunction(x => WhatIsMyName);
Обратите внимание, что это зависит от неопределенного поведения, и хотя оно работает в текущих компиляторах C# и VB Microsoft и в компиляторе C# Mono, нет гарантии, что это не перестанет работать в будущих версиях.
Я знаю, что этот пост действительно старый, но, поскольку теперь в .Net 6 есть способ, я решил поделиться им, чтобы другие знали.
Теперь вы можете использовать CallerArgumentExpressionAttribute, как показано ниже.
/// <summary>
/// Will throw argument exception if string IsNullOrEmpty returns true
/// </summary>
/// <param name="str"></param>
/// <param name="str_name"></param>
/// <exception cref="ArgumentException"></exception>
public static void ValidateNotNullorEmpty(this string str,
[CallerArgumentExpression("str")]string str_name=null)
{
if (string.IsNullOrEmpty(str))
{
throw new ArgumentException($"'{str_name}' cannot be null or empty.", str_name);
}
}
Теперь позвоните с помощью:
param.ValidateNotNullorEmpty();
выдаст ошибку: «параметр не может быть нулевым или пустым».
вместо "str не может быть нулевым или пустым"
Я знаю, что это старый вопрос, но в C# 6.0 они вводят имя оператора, который должен решить эту проблему. Имя оператора разрешает имя передаваемой в него переменной.
Использование для вашего случая будет выглядеть так:
public string ExampleFunction(string variableName) {
//Construct your log statement using c# 6.0 string interpolation
return $"Error occurred in {variableName}";
}
string WhatIsMyName = "Hello World"';
string Hello = ExampleFunction(nameof(WhatIsMyName));
Основным преимуществом является то, что это делается во время компиляции,
Имя выражения является константой. Во всех случаях nameof (...) вычисляется во время компиляции для создания строки. Его аргумент не оценивается во время выполнения и считается недоступным кодом (однако он не выдает предупреждение "недоступный код").
Более подробную информацию можно найти здесь
Старая версия C 3.0 и выше
Чтобы построить на Nawfals ответ
GetParameterName2(new { variable });
//Hack to assure compiler warning is generated specifying this method calling conventions
[Obsolete("Note you must use a single parametered AnonymousType When Calling this method")]
public static string GetParameterName<T>(T item) where T : class
{
if (item == null)
return string.Empty;
return typeof(T).GetProperties()[0].Name;
}
static void Main(string[] args)
{
Console.WriteLine("Name is '{0}'", GetName(new {args}));
Console.ReadLine();
}
static string GetName<T>(T item) where T : class
{
var properties = typeof(T).GetProperties();
Enforce.That(properties.Length == 1);
return properties[0].Name;
}
Более подробная информация в этом блоге.
Три способа:
1) Нечто вообще не задумываясь
GetParameterName1(new { variable });
public static string GetParameterName1<T>(T item) where T : class
{
if (item == null)
return string.Empty;
return item.ToString().TrimStart('{').TrimEnd('}').Split('=')[0].Trim();
}
2) Использует отражение, но это намного быстрее, чем два других.
GetParameterName2(new { variable });
public static string GetParameterName2<T>(T item) where T : class
{
if (item == null)
return string.Empty;
return typeof(T).GetProperties()[0].Name;
}
3) Самый медленный из всех, не используйте.
GetParameterName3(() => variable);
public static string GetParameterName3<T>(Expression<Func<T>> expr)
{
if (expr == null)
return string.Empty;
return ((MemberExpression)expr.Body).Member.Name;
}
Чтобы получить имя и значение комбинированного параметра, вы можете расширить эти методы. Конечно, получить значение легко, если передать параметр отдельно в качестве другого аргумента, но это не элегантно. Вместо:
1)
public static string GetParameterInfo1<T>(T item) where T : class
{
if (item == null)
return string.Empty;
var param = item.ToString().TrimStart('{').TrimEnd('}').Split('=');
return "Parameter: '" + param[0].Trim() +
"' = " + param[1].Trim();
}
2)
public static string GetParameterInfo2<T>(T item) where T : class
{
if (item == null)
return string.Empty;
var param = typeof(T).GetProperties()[0];
return "Parameter: '" + param.Name +
"' = " + param.GetValue(item, null);
}
3)
public static string GetParameterInfo3<T>(Expression<Func<T>> expr)
{
if (expr == null)
return string.Empty;
var param = (MemberExpression)expr.Body;
return "Parameter: '" + param.Member.Name +
"' = " + ((FieldInfo)param.Member).GetValue(((ConstantExpression)param.Expression).Value);
}
1 и 2 теперь сравнимой скорости, 3 снова вялый.
Да! Это возможно. Я долго искал решение этой проблемы и, наконец, придумал хак, который ее решает (это немного неприятно). Я бы не рекомендовал использовать это как часть вашей программы, и я думаю, что она работает только в режиме отладки. Для меня это не имеет значения, так как я использую его только как инструмент отладки в своем классе консоли, поэтому я могу сделать:
int testVar = 1;
bool testBoolVar = True;
myConsole.Writeline(testVar);
myConsole.Writeline(testBoolVar);
вывод на консоль будет:
testVar: 1
testBoolVar: True
Вот функция, которую я использую для этого (не включая код переноса для моего консольного класса).
public Dictionary<string, string> nameOfAlreadyAcessed = new Dictionary<string, string>();
public string nameOf(object obj, int level = 1)
{
StackFrame stackFrame = new StackTrace(true).GetFrame(level);
string fileName = stackFrame.GetFileName();
int lineNumber = stackFrame.GetFileLineNumber();
string uniqueId = fileName + lineNumber;
if (nameOfAlreadyAcessed.ContainsKey(uniqueId))
return nameOfAlreadyAcessed[uniqueId];
else
{
System.IO.StreamReader file = new System.IO.StreamReader(fileName);
for (int i = 0; i < lineNumber - 1; i++)
file.ReadLine();
string varName = file.ReadLine().Split(new char[] { '(', ')' })[1];
nameOfAlreadyAcessed.Add(uniqueId, varName);
return varName;
}
}
Продолжая
Caller*
серия атрибутов (т. е.
CallerMemberName
,
CallerFilePath
и
CallerLineNumber
), CallerArgumentExpressionAttribute доступен начиная с C # Next (подробнее здесь ).
Следующий пример основан на CallerArgumentExpression Пола Макилриви атрибутев C # 8.0 :
public static void ThrowIfNullOrWhitespace(this string self,
[CallerArgumentExpression("self")] string paramName = default)
{
if (self is null)
{
throw new ArgumentNullException(paramName);
}
if (string.IsNullOrWhiteSpace(self))
{
throw new ArgumentOutOfRangeException(paramName, self, "Value cannot be whitespace");
}
}
Это было бы очень полезно для создания хороших сообщений об исключениях, позволяющих людям лучше выявлять ошибки. Номера строк помогают, но вы можете не получить их в продукте, и когда вы их получите, если в коде есть большие операторы, вы обычно получите только первую строку всего оператора.
Например, если вы вызываете.Value для значения NULL, которое не задано, вы получите исключение с сообщением об ошибке, но, поскольку этой функции не хватает, вы не увидите, какое свойство было нулевым. Если вы сделаете это дважды в одном операторе, например, чтобы установить параметры для некоторого метода, вы не сможете увидеть, какие значения nullable не были установлены.
Создание кода вроде Verify.NotNull(myvar, nameof(myvar)) - лучший обходной путь, который я нашел до сих пор, но было бы здорово избавиться от необходимости добавлять дополнительный параметр.
Нет, но всякий раз, когда вы обнаруживаете, что делаете очень сложные вещи, подобные этой, вы можете пересмотреть свое решение. Помните, что код должен быть легче читать, чем писать.
System.Environment.StackTrace выдаст вам строку, содержащую текущий стек вызовов. Вы можете проанализировать это, чтобы получить информацию, которая включает имена переменных для каждого вызова.
Сделай это
var myVariable = 123;
myVariable.Named(() => myVariable);
var name = myVariable.Name();
// use name how you like
или именование в коде от руки
var myVariable = 123.Named("my variable");
var name = myVariable.Name();
используя этот класс
public static class ObjectInstanceExtensions
{
private static Dictionary<object, string> namedInstances = new Dictionary<object, string>();
public static void Named<T>(this T instance, Expression<Func<T>> expressionContainingOnlyYourInstance)
{
var name = ((MemberExpression)expressionContainingOnlyYourInstance.Body).Member.Name;
instance.Named(name);
}
public static T Named<T>(this T instance, string named)
{
if (namedInstances.ContainsKey(instance)) namedInstances[instance] = named;
else namedInstances.Add(instance, named);
return instance;
}
public static string Name<T>(this T instance)
{
if (namedInstances.ContainsKey(instance)) return namedInstances[instance];
throw new NotImplementedException("object has not been named");
}
}
Код проверен и самый элегантный, который я могу придумать.
Хорошо попробуй этот класс Utility,
public static class Utility
{
public static Tuple<string, TSource> GetNameAndValue<TSource>(Expression<Func<TSource>> sourceExpression)
{
Tuple<String, TSource> result = null;
Type type = typeof (TSource);
Func<MemberExpression, Tuple<String, TSource>> process = delegate(MemberExpression memberExpression)
{
ConstantExpression constantExpression = (ConstantExpression)memberExpression.Expression;
var name = memberExpression.Member.Name;
var value = ((FieldInfo)memberExpression.Member).GetValue(constantExpression.Value);
return new Tuple<string, TSource>(name, (TSource) value);
};
Expression exception = sourceExpression.Body;
if (exception is MemberExpression)
{
result = process((MemberExpression)sourceExpression.Body);
}
else if (exception is UnaryExpression)
{
UnaryExpression unaryExpression = (UnaryExpression)sourceExpression.Body;
result = process((MemberExpression)unaryExpression.Operand);
}
else
{
throw new Exception("Expression type unknown.");
}
return result;
}
}
И пользователю это нравится
/*ToDo : Test Result*/
static void Main(string[] args)
{
/*Test : primivit types*/
long maxNumber = 123123;
Tuple<string, long> longVariable = Utility.GetNameAndValue(() => maxNumber);
string longVariableName = longVariable.Item1;
long longVariableValue = longVariable.Item2;
/*Test : user define types*/
Person aPerson = new Person() { Id = "123", Name = "Roy" };
Tuple<string, Person> personVariable = Utility.GetNameAndValue(() => aPerson);
string personVariableName = personVariable.Item1;
Person personVariableValue = personVariable.Item2;
/*Test : anonymous types*/
var ann = new { Id = "123", Name = "Roy" };
var annVariable = Utility.GetNameAndValue(() => ann);
string annVariableName = annVariable.Item1;
var annVariableValue = annVariable.Item2;
/*Test : Enum tyoes*/
Active isActive = Active.Yes;
Tuple<string, Active> isActiveVariable = Utility.GetNameAndValue(() => isActive);
string isActiveVariableName = isActiveVariable.Item1;
Active isActiveVariableValue = isActiveVariable.Item2;
}
благодаря Visual Studio 2022 вы можете использовать это
функция
public void showname(dynamic obj) {
obj.GetType().GetProperties().ToList().ForEach(state => {
NameAndValue($"{state.Name}:{state.GetValue(obj, null).ToString()}");
});
}
использовать
var myname = "dddd";
showname(new { myname });
Спасибо за все ответы. Я думаю, мне просто нужно идти с тем, что я делаю сейчас.
Для тех, кто хотел знать, почему я задал вышеуказанный вопрос. У меня есть следующая функция:
string sMessages(ArrayList aMessages, String sType) {
string sReturn = String.Empty;
if (aMessages.Count > 0) {
sReturn += "<p class=\"" + sType + "\">";
for (int i = 0; i < aMessages.Count; i++) {
sReturn += aMessages[i] + "<br />";
}
sReturn += "</p>";
}
return sReturn;
}
Я посылаю ему массив сообщений об ошибках и класс CSS, который затем возвращается в виде строки для веб-страницы.
Каждый раз, когда я вызываю эту функцию, я должен определить sType. Что-то вроде:
output += sMessages(aErrors, "errors");
Как видите, мои переменные называются aErrors, а мой класс css - ошибками. Я надеялся, что моя простуда сможет выяснить, какой класс использовать, основываясь на имени переменной, которую я ему отправил.
Еще раз спасибо за все ответы.
GateKiller, что не так с моим решением? Вы можете переписать свою функцию тривиально, чтобы использовать ее (я позволил себе улучшить функцию на лету):
static string sMessages(Expression<Func<List<string>>> aMessages) {
var messages = aMessages.Compile()();
if (messages.Count == 0) {
return "";
}
StringBuilder ret = new StringBuilder();
string sType = ((MemberExpression)aMessages.Body).Member.Name;
ret.AppendFormat("<p class=\"{0}\">", sType);
foreach (string msg in messages) {
ret.Append(msg);
ret.Append("<br />");
}
ret.Append("</p>");
return ret.ToString();
}
Назовите это так:
var errors = new List<string>() { "Hi", "foo" };
var ret = sMessages(() => errors);
Расширяя принятый ответ на этот вопрос, вот как вы это сделаете с#nullable enable
исходные файлы:
internal static class StringExtensions
{
public static void ValidateNotNull(
[NotNull] this string? theString,
[CallerArgumentExpression("theString")] string? theName = default)
{
if (theString is null)
{
throw new ArgumentException($"'{theName}' cannot be null.", theName);
}
}
public static void ValidateNotNullOrEmpty(
[NotNull] this string? theString,
[CallerArgumentExpression("theString")] string? theName = default)
{
if (string.IsNullOrEmpty(theString))
{
throw new ArgumentException($"'{theName}' cannot be null or empty.", theName);
}
}
public static void ValidateNotNullOrWhitespace(
[NotNull] this string? theString,
[CallerArgumentExpression("theString")] string? theName = default)
{
if (string.IsNullOrWhiteSpace(theString))
{
throw new ArgumentException($"'{theName}' cannot be null or whitespace", theName);
}
}
}
Что хорошо в этом коде, так это то, что он использует [NotNull]
атрибут, поэтому статический анализ будет сотрудничать:
Чтобы получить его, можно прочитать файл кода и разделить его запятыми и скобками ...
var trace = new StackTrace(true).GetFrame(1);
var line = File.ReadAllLines(trace.GetFileName())[trace.GetFileLineNumber()];
var argumentNames = line.Split(new[] { ",", "(", ")", ";" },
StringSplitOptions.TrimEntries)
.Where(x => x.Length > 0)
.Skip(1).ToList();
Нет. Ссылка на вашу строковую переменную передается в функцию - в нее не включены никакие присущие ей метадеты. Даже рефлексия не сможет вытащить вас из леса - работа в обратном направлении от одного ссылочного типа не даст вам достаточно информации, чтобы сделать то, что вам нужно.
Лучше вернитесь к чертежной доске на этом!
Р.П.
Вы можете использовать отражение, чтобы получить все свойства объекта, а затем пройти через него и получить значение свойства, в котором имя (свойства) соответствует переданному параметру.
Короткий ответ - нет... если вы не очень мотивированы.
Единственный способ сделать это - через рефлексию и стековую ходьбу. Вам нужно будет получить кадр стека, определить местонахождение вызывающей функции, из которой вы были вызваны, и затем с помощью CodeDOM попытаться найти правую часть дерева, чтобы увидеть, что это за выражение.
Например, что, если вызов был ExampleFunction("a" + "b")?
Хорошо было немного посмотреть. конечно, вы не можете использовать любую информацию о типе. Кроме того, имя локальной переменной недоступно во время выполнения, поскольку их имена не скомпилированы в метаданные сборки.
Если я вас правильно понял, вы хотите, чтобы строка «WhatIsMyName» отображалась внутри строки Hello.
string Hello = ExampleFunction(WhatIsMyName);
Если вариант использования заключается в том, что он увеличивает возможность повторного использования ExampleFunction и этот Hello должен содержать что-то вроде «Hello, Peter (from WhatIsMyName)», то я думаю, что решением было бы расширить ExampleFunction, чтобы принять:
строка Hello = ExampleFunction (WhatIsMyName, nameof (WhatIsMyName));
Так что имя передается отдельной строкой. Да, это не совсем то, что вы просили, и вам придется ввести его дважды. Но он безопасен для рефакторинга, удобочитаем, не использует интерфейс отладки, и вероятность ошибки минимальна, потому что они появляются вместе в используемом коде.
string Hello1 = ExampleFunction(WhatIsMyName,nameof(WhatIsMyName));
string Hello2 = ExampleFunction(SomebodyElse,nameof(SomebodyElse));
string Hello3 = ExampleFunction(HerName,nameof(HerName));