Любой код, который дублирует, как DebuggerDisplayAttribute генерирует результирующую строку?
Любой знает любой код, который дублирует, как DebuggerDisplayAttribute
разбирает и собирает результирующую строку?
Я хотел бы создать пользовательский атрибут, который выполняет почти примерную работу. Аналогично "Когда достигается точка останова...", где вы можете использовать переменную в фигурных скобках, как в "{variable}".
Я уже обрабатываю простые случаи, такие как "{Name}", но что-то вроде "{Foo.Name}" требует дополнительного кода отражения, с которым мне нужна помощь.
По сути, я хочу проанализировать строку, используя правила, определенные в DebuggerDisplayAttribute
документация. В настоящее время я могу разобрать и решить "Я {GetName()}". Мне нужна помощь с чем-то вроде "Foo's Name: {Foo.Name}"
2 ответа
Надеюсь, этот код подходит всем... Я сделал версию того, что вы пытаетесь сделать, без отражения, используя Microsoft Roslyn и его возможность C# Scripting для запуска "кода" в значении атрибута как кода C#.
Чтобы использовать этот код, создайте новый проект на C# и используйте NuGet, чтобы добавить ссылку на Roslyn.
Сначала классы, которые я использую для тестирования, чтобы вы могли видеть атрибуты, которые я пробовал.
using System.Diagnostics;
namespace DebuggerDisplayStrings
{
[DebuggerDisplay("The Value Is {StringProp}.")]
public class SomeClass
{
public string StringProp { get; set; }
}
[DebuggerDisplay("The Value Is {Foo.StringProp}.")]
public class SomeClass2
{
public SomeClass Foo { get; set; }
}
[DebuggerDisplay("The Value Is {Seven() - 6}.")]
public class SomeClass3
{
public int Seven()
{
return 7;
}
}
}
Теперь тесты (да, все они проходят):
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DebuggerDisplayStrings
{
[TestClass]
public class DebuggerDisplayReaderTests
{
[TestMethod]
public void CanReadStringProperty()
{
var target = new SomeClass {StringProp = "Foo"};
var reader = new DebuggerDisplayReader();
Assert.AreEqual("The Value Is Foo.", reader.Read(target));
}
[TestMethod]
public void CanReadPropertyOfProperty()
{
var target = new SomeClass2 {Foo = new SomeClass {StringProp = "Foo"}};
var reader = new DebuggerDisplayReader();
Assert.AreEqual("The Value Is Foo.", reader.Read(target));
}
[TestMethod]
public void CanReadMethodResultAndDoMath()
{
var target = new SomeClass3();
var reader = new DebuggerDisplayReader();
Assert.AreEqual("The Value Is 1.", reader.Read(target));
}
}
}
Наконец, реальные товары:
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
using Roslyn.Scripting.CSharp;
namespace DebuggerDisplayStrings
{
public class DebuggerDisplayReader
{
// Get the fully evaluated string representation of the DebuggerDisplayAttribute's value.
public string Read(object target)
{
var debuggerDisplayFormat = GetDebuggerDisplayFormat(target);
if(string.IsNullOrWhiteSpace(debuggerDisplayFormat))
return target.ToString();
return EvaluateDebuggerDisplayFormat(debuggerDisplayFormat, target);
}
// Gets the string off the attribute on the target class, or returns null if attribute not found.
private static string GetDebuggerDisplayFormat(object target)
{
var attributes = target.GetType().GetCustomAttributes(typeof(DebuggerDisplayAttribute), false);
return attributes.Length > 0 ? ((DebuggerDisplayAttribute)attributes[0]).Value : null;
}
// Executes each bracketed portion of the format string using Roslyn,
// and puts the resulting value back into the final output string.
private string EvaluateDebuggerDisplayFormat(string format, object target)
{
var scriptingEngine = new ScriptEngine(new[] { GetType().Assembly });
var formatInfo = ExtractFormatInfoFromFormatString(format);
var replacements = new List<object>(formatInfo.FormatReplacements.Length);
foreach (var codePart in formatInfo.FormatReplacements)
{
var result = scriptingEngine.Execute(codePart, target);
replacements.Add((result ?? "").ToString());
}
return string.Format(formatInfo.FormatString, replacements.ToArray());
}
// Parse the format string from the attribute into its bracketed parts.
// Prepares the string for string.Format() replacement.
private static DebuggerDisplayFormatInfo ExtractFormatInfoFromFormatString(string format)
{
var result = new DebuggerDisplayFormatInfo();
var regex = new Regex(@"\{(.*)\}");
var matches = regex.Matches(format);
result.FormatReplacements = new string[matches.Count];
for (var i = matches.Count - 1; i >= 0; i-- )
{
var match = matches[i];
result.FormatReplacements[i] = match.Groups[1].Value;
format = format.Remove(match.Index + 1, match.Length - 2).Insert(match.Index+1, i.ToString(CultureInfo.InvariantCulture));
}
result.FormatString = format;
return result;
}
}
internal class DebuggerDisplayFormatInfo
{
public string FormatString { get; set; }
public string[] FormatReplacements { get; set; }
}
}
Надеюсь, это поможет вам. Это было всего около полутора часов работы, поэтому модульное тестирование не завершено никакими средствами, и я уверен, что где-то там есть ошибки, но это должно быть надежным началом, если вы согласны с Рослин подход.
Я предполагаю, что это для вашего (командного) использования. Я лично не пробовал это, но вы смотрели объяснения, как настроить атрибут DebuggerDisplay, найденный здесь?