Страницы

Поиск по вопросам

вторник, 31 декабря 2019 г.

Когда в c# class не совсем class?

#c_sharp #net


В одном проекте разбирал багу. Эта бага была связана с тем, что проект прошел некоторую
техническую итерацию,  т.е. бизнес логика в нём  не поменялась, но в него была добавлена
технология, которая неявно поменяла поведение кода, оставив его представление неизменным.
И связано это именно с тем, что в некоторых случая class бывает не совсем class-ом.
Лабораторная по анализу этого бага приобрела форму задачки на знание технологии .NET.
Таким образом, прошу оценить формулировку задачки и её актуальность.
Ну, и если захотите, можете представить её решение. :)  

Задачка  

Есть код консольного приложения на c#:

#region Здесь код изменять нельзя
public sealed class ClassA
{
    private int _value;

    public int Value
    {
        get { return _value; }
        set { _value = value; }
    }
}
#endregion

#region Здесь код изменять нельзя
public sealed class ClassB
#endregion
#region Здесь код изменять нельзя
{
    public void Do(ref ClassA a)
    {
        a.Value = 5;
    }
}
#endregion

public class Program
{
    public static void Main(string[] args)
    {
        #region Для класс Program изменять код только здесь
        ClassB b = new ClassB();
        #endregion

        bool result = Process(b);

        System.Console.WriteLine(result);
    }

    public static bool Process(ClassB b)
    {
        ClassA a1 = new ClassA();
        ClassA a2 = a1;

        b.Do(ref a2);

        return a1.Value == a2.Value;
    }
}


Запустив его на исполнение мы увидим, что  метод Program.Process вернёт значение
true и на консоль будет выведен текст «true».  

Используя стандартный функционал .NET Framework (версией >= 2.0)  допишите код для
классов ClassA и ClassB, а также измените код в методе Program.Main, чтобы метод Program.Process
вернул значение false и, соответственно, на консоль должно быть выведено «false».
Код в регионах #region Здесь код изменять нельзя менять нельзя. Для класса Program
код можно поменять только в регионе region Для класс Program изменять код только здесь.  

UPD Спасибо @DreamChild
Директивы условной компиляции C# не применять.  

UPD
Хотел бы ещё раз уточнить, что нужно только дописать ClassA и ClassB, т.е. не нужно
создавать новые классы.  

UPD  Ответ дал @hazzik
Суть в том, что выполнение функционал класса ClassB перевели в другой домен. Но при
этом неявно поменялась логика работы метода ClassB.Do(ref ClassA) и экземпляр типа
ClassA уже передаётся не как ссылочный объект, а как значимый объект, т.е. в результате
сериализации получается структура значений полей класса ClassA, которая передается
через границу домена. При выходе из метода ClassB.Do(ref ClassA) возвращается структура
полей класс ClassA и, самое интересное, эта структура десериализуется в новый объект,
ссылка на который и записывается в переменную a2. Таким образом, в a1 и в a2 получаются
ссылки на два разных объекта, при чём, значения их внутренних полей различаются.  
    


Ответы

Ответ 1



То же самое, что и предыдущий мой ответ, только все это делаем средствами .NET fx, т.е. используем его прокси и, соответственно, все вызываем из отдельного домена: [Serializable] #region Здесь код изменять нельзя public sealed class ClassA { private int _value; public int Value { get { return _value; } set { _value = value; } } } #endregion #region Здесь код изменять нельзя public sealed class ClassB #endregion : MarshalByRefObject #region Здесь код изменять нельзя { public void Do(ref ClassA a) { a.Value = 5; } } #endregion public class Program { public static void Main(string[] args) { #region Для класс Program изменять код только здесь ClassB b = (ClassB) AppDomain.CreateDomain("X").CreateInstanceFromAndUnwrap(typeof(ClassB).Assembly.CodeBase, typeof(ClassB).FullName); #endregion bool result = Process(b); Console.WriteLine(result); } public static bool Process(ClassB b) { ClassA a1 = new ClassA(); ClassA a2 = a1; b.Do(ref a2); return a1.Value == a2.Value; } } Почему так происходит? ClassA не помечен для маршаллинга через границу доменов с передачей по ссылке (то, что делает MarshalByRefObject), по-этому он будет сериализован до передачи в домен "X" и в домене "X" десериализован в новый объект. Для внешнего домена никто никогда не изменял a1 его Value так и останется 0. При возвращении из домена "X" экземпляр класса ClassA будет сериализован и десериализован уже в обратном направлении и присвоен переменной a2. Если у параметра a метода ClassB.Do убрать модификатор ref то картина будет немного иной: обратной десериализации из "X" в текущий домен происходить не будет, и, следовательно, a2 будет все-еще ссылаться на тот же объект, что и a1, а значение свойства Value этого объекта останется равным 0. Чтобы исправить эти "проблемы" нужно унаследовать ClassA от MarshalByRefObject.

Ответ 2



Так задумано или тут дыра в регионах? ) #region Здесь код изменять нельзя public sealed class ClassB #endregion // сюда можно дописать что угодно, например: : BaseB { public new void Do(ref ClassA a) { a = new ClassA(); base.Do(ref a); } } public class BaseB #region Здесь код изменять нельзя { public void Do(ref ClassA a) { a.Value = 5; } } #endregion

Ответ 3



ClassB наследуем от System.MarshalByRefObject Пишем следующий прокси-класс: class ClassBProxy : RealProxy { public ClassBProxy() : base(typeof (ClassB)) { } public override IMessage Invoke(IMessage msg) { IMethodCallMessage call = msg as IMethodCallMessage; var args = call.Args; // Здесь мы подменяем ClassA на нужный нам args[0] = new ClassA {Value = 100}; return new ReturnMessage(null, call.Args, call.Args.Length, call.LogicalCallContext, call); } } В Program.Main создаем ClassB вот так: ClassB b = (ClassB) new ClassBProxy().GetTransparentProxy(); Полный пример: https://dotnetfiddle.net/GbhXP5

Ответ 4



Сделать это можно с помощью вот такого колдунства: модифицируем класс ClassB таким образом: #region Здесь код изменять нельзя public sealed class ClassB #endregion #if SOME_FLAG // SOME_FLAG нигде не определен, // а потому "старая" реализация метода Do // заменяется "новой" из ветки №else #region Здесь код изменять нельзя { public void Do(ref ClassA a) { a.Value = 5; } } #endregion #else { public void Do(ref ClassA a) { a = new ClassA(); a.Value = 6; } } #endif менять код в Main даже не потребовалось

Комментариев нет:

Отправить комментарий