Это законный код для пометки такого объекта?

Однажды я написал фрагмент кода для добавления имени к задаче. Код ниже делает то же самое, но с меньшим количеством кода. Но интересно, законно ли это. Готов ли производственный код. А сбор мусора? А как насчет экземпляра класса, перемещаемого в коде (поскольку он не закреплен), будет ли он по-прежнему работать при перемещении? Как я могу проверить этот код?

using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var obj = new object();
            obj.Tag("Link some data");
            var tag = obj.Tag();
        }
    }

    public static class ObjectExtensions
    {
        private class Tagger
        {
            public string Tag { get; set; }
        }

        [StructLayout(LayoutKind.Explicit)]
        private struct Overlay
        {
            [FieldOffset(0)]
            public Tagger Tagger;
            [FieldOffset(0)]
            public object Instance;
        }

        public static string Tag(this object obj)
        {
            var overlay = new Overlay {Instance = obj };
            return overlay.Tagger.Tag;
        }

        public static void Tag(this object obj, string tag)
        {
            var overlay = new Overlay {Instance = obj };
            overlay.Tagger.Tag = tag;
        }
    }
}

person Mike de Klerk    schedule 07.04.2017    source источник


Ответы (1)


Нет, это вообще не правомерно. Честно говоря, я удивлен, что .NET и C# позволяют это без переключателя /unsafe. В вашем предложении есть очевидные риски, но я должен признать, что за все эти годы кодирования на C# мне никогда не приходило в голову, что можно нарушить безопасный доступ к памяти, подобный этому в C#, без явного включения небезопасного кода.

Рассмотрим этот вариант на вашем примере:

class A
{
    public string Text { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        A a = new A { Text = "Text" };

        a.Tag("object A tag");

        string tag = a.Tag(), text = a.Text;
    }
}

Вы обнаружите, что в последнем операторе переменная text была установлена ​​на "object A Tag". Другими словами, ваш "наложенный" подход позволил вашему коду переинтерпретировать ссылку на объект класса A как ссылку на объект класса Overlay без какого-либо предупреждения компилятора или ошибки во время выполнения.

В приведенном выше примере последствия настолько безобидны, насколько вы могли надеяться: исходное значение свойства Text было изменено с его правильного значения на текст, переданный как «тег». Это достаточно плохо, но в других контекстах вы можете обнаружить, что ваш класс был поврежден ужасным образом, что приведет к дальнейшему повреждению данных или (если вам повезет) к немедленному завершению вашей программы в результате какого-либо нарушения прав доступа или другого исключения. .

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

person Peter Duniho    schedule 07.04.2017
comment
Сегодня вечером я провел некоторое время, изучая этот вопрос, и нашел несколько интересных вещей: кажется, что изменение Tagger на структуру заставляет CLR записывать данные в конец объекта, который разделяет структура Overlay, поэтому ничего явно плохого там не происходит. Кроме того, если исходный объект представляет собой массив int[], а int записывается в Tagger, 64 байта в памяти от начального указателя содержат некоторое порядковое число (массив int[3] приводит к этому числу, равному +60) из first int, но если написано что-то еще, это число совпадает с первым int... Это действительно круто. - person Scott; 08.04.2017