Любой код, который дублирует, как DebuggerDisplayAttribute генерирует результирующую строку?

Кто-нибудь знает какой-либо код, который дублирует, как DebuggerDisplayAttribute анализирует и собирает результирующую строку?

Я хотел бы создать пользовательский атрибут, который почти подходит к образцу. Синтаксис "Когда ударяется точка останова...", где вы можете использовать переменную в фигурных скобках, как в "{variable}".

Я уже обрабатываю простые случаи, такие как "{Name}", но что-то вроде "{Foo.Name}" требует дополнительного кода отражения, в котором мне нужна помощь.

В принципе, я хочу проанализировать строку, используя правила, определенные в документации DebuggerDisplayAttribute. В настоящее время я могу разобрать и решить "Я {GetName()}". Мне нужна помощь с чем-то вроде "Foo Name: {Foo.Name}"

Ответ 1

Надеюсь, этот код все подходит... Я сделал нереляционную версию того, что вы пытаетесь сделать, используя Microsoft Roslyn и его способность С# Scripting запускать "код" в значении атрибута как код С#.

Чтобы использовать этот код, создайте новый проект С# и используйте 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 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; }
    }
}

Надеюсь, это поможет вам. Это было всего около полутора часов работы, поэтому модульное тестирование не завершено каким-либо образом, и я уверен, что там где-то есть ошибки, но это должен быть прочный старт, если вы в порядке с Рослин.

Ответ 2

Я предполагаю, что это для вашего собственного (коллективного) использования. Я лично не пробовал это, но посмотрели ли вы на объяснения, как настроить атрибут DebuggerDisplay, найденный здесь?