[Этюд] Сломанный деструктор
От: Mab Россия http://shade.msu.ru/~mab
Дата: 20.09.10 19:56
Оценка: 66 (4)
Рассмотрим следующий класс:
class Goodbye
{
  public Goodbye()
  {
     Console.WriteLine("hello");
  }

  ~Goodbye()
  {
     Console.WriteLine("goodbye");
  }
}


Тогда в обычных обстоятельствах код
static void Main()
{
   new Goodbye();
   GC.Collect();
   GC.WaitForPendingFinalizers();
}

выведет на консоль
hello
goodbye


Однако если вместо
class Goodbye

написать
class Goodbye : DontSayGoodbye

то выведено будет только
hello


Как устроен DontSayGoodbye?

Ограничение: DontSayGoodbye не должен иметь конструктора по умолчанию.
Re: [Этюд] Сломанный деструктор
От: relusion Россия  
Дата: 20.09.10 20:08
Оценка:
это с конструктором.
 public class DontSayGoodbye
    {
        public DontSayGoodbye()
        {
            GC.SuppressFinalize(this);
        }
    }


Думаю как сделать без него
Re: [Этюд] Сломанный деструктор
От: MozgC США http://nightcoder.livejournal.com
Дата: 20.09.10 21:13
Оценка:
Здравствуйте, Mab, Вы писали:

Mab>Ограничение: DontSayGoodbye не должен иметь конструктора по умолчанию.


Т.е. код класса Goodbye надо будет изменить чтобы он вызывал конструктор класса DontSayGoodbye с параметром?
Re: [Этюд] Сломанный деструктор
От: k.o. Россия  
Дата: 20.09.10 21:53
Оценка:
Здравствуйте, Mab, Вы писали:

Mab>Рассмотрим следующий класс:


Mab>Как устроен DontSayGoodbye?


Очевидный ответ:

class Base
{
    public Base()
    {
        GC.SuppressFinalize(this);
    }
}

class DontSayGoodbye : Base
{
}




Mab>Ограничение: DontSayGoodbye не должен иметь конструктора по умолчанию.


Ограничение, вроде, не нарушил, но ты, наверно, другое решение имел в виду.
Re[2]: [Этюд] Сломанный деструктор
От: MozgC США http://nightcoder.livejournal.com
Дата: 20.09.10 22:01
Оценка:
Здравствуйте, k.o., Вы писали:

KO>Ограничение, вроде, не нарушил, но ты, наверно, другое решение имел в виду.


Я сначала написал такой ответ, потом отправил пост в корзину. Все-таки класс DontSayGoodbye имеет конструктор по-умолчанию, что легко можно увидеть в рефлекторе.
Re[3]: [Этюд] Сломанный деструктор
От: Mab Россия http://shade.msu.ru/~mab
Дата: 21.09.10 04:08
Оценка:
Здравствуйте, MozgC, Вы писали:

Да, делегирующий конструктор, создаваемый компилятором, тоже не допустим. Именно поэтому и написано "имеет", а не "определяет".
Re[2]: [Этюд] Сломанный деструктор
От: Mab Россия http://shade.msu.ru/~mab
Дата: 21.09.10 04:10
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Т.е. код класса Goodbye надо будет изменить чтобы он вызывал конструктор класса DontSayGoodbye с параметром?

Нет, Goodbye менять нельзя.
Более того, в моем решении ни один из методов, определенных в DontSayGoodbye (явно или неявно), не выполняется.
Re[3]: [Этюд] Сломанный деструктор
От: Tepp  
Дата: 21.09.10 04:58
Оценка: 14 (2)
class Goodbye : DontSayGoodbye
{
    public Goodbye()
    {
        Console.WriteLine("hello");
    }
    ~Goodbye()
    {
        Console.WriteLine("goodbye");
    }
}

class DontSayGoodbye 
{
    public DontSayGoodbye(params object[] objs)
    {
        GC.SuppressFinalize(this);
    }
}
Re[4]: [Этюд] Сломанный деструктор
От: Mab Россия http://shade.msu.ru/~mab
Дата: 21.09.10 05:06
Оценка:
Здравствуйте, Tepp, Вы писали:

Убедительно, но это совсем не то, чего хотелось бы

Уточню: DontSayGoodbye не должен иметь никаких конструкторов (включая делегирующий, создаваемый компилятором).
Re: [Этюд] Сломанный деструктор
От: k.o. Россия  
Дата: 21.09.10 06:00
Оценка: 12 (1) +1 :)
Здравствуйте, Mab, Вы писали:

Mab>Как устроен DontSayGoodbye?


Подозреваю, что опять мимо, но тем не менее:


    class DontSayGoodbye
    {
        protected class Console
        {
            static int writeCalls = 0;

            public static void WriteLine(string s)
            {
                if (writeCalls > 0)
                {
                    return;
                }

                System.Console.WriteLine(s);
                ++writeCalls;
            }
        }
    }




Mab>Ограничение: DontSayGoodbye не должен иметь конструктора по умолчанию.
Re[2]: [Этюд] Сломанный деструктор
От: Mab Россия http://shade.msu.ru/~mab
Дата: 21.09.10 06:02
Оценка:
Здравствуйте, k.o., Вы писали:

Класс Console, конечно, стандартный.
Re: [Этюд] Сломанный деструктор
От: shakm Россия  
Дата: 21.09.10 06:29
Оценка:
скорее всего имеется ввиду какой-то класс, от которго наследован DontSayGoodbye, у которого что-то там сломано :-D
Re: [Этюд] Сломанный деструктор
От: Aznog Россия  
Дата: 21.09.10 07:04
Оценка: 226 (12)
>> ...
>> Как устроен DontSayGoodbye?
    abstract class DontSayGoodbye
    {
        protected abstract void Finalize();
    }
Re[2]: [Этюд] Сломанный деструктор
От: Mab Россия http://shade.msu.ru/~mab
Дата: 21.09.10 07:09
Оценка:
Здравствуйте, Aznog, Вы писали:

Да, именно так!
Re[3]: [Этюд] Сломанный деструктор
От: Tepp  
Дата: 21.09.10 07:26
Оценка:
А как же :"Уточню: DontSayGoodbye не должен иметь никаких конструкторов (включая делегирующий, создаваемый компилятором)." ? =)
Re[4]: [Этюд] Сломанный деструктор
От: Mab Россия http://shade.msu.ru/~mab
Дата: 21.09.10 07:33
Оценка:
Здравствуйте, Tepp, Вы писали:

Согласен, от делегирующего конструктора не избавиться. Можно потребовать, чтобы DontSayGoodbye наследовался напрямую от object и не определял явно конструктор(ов).
Re: [Этюд] Сломанный деструктор
От: _FRED_ Черногория
Дата: 21.09.10 08:33
Оценка: 57 (6)
Здравствуйте, Mab, Вы писали:

Mab>Как устроен DontSayGoodbye?


И мои пять копеек:
[DontSayGoodbye]
class DontSayGoodbye : ContextBoundObject
{
  internal sealed class DontSayGoodbyeAttribute : ProxyAttribute
  {
    private static object value;

    public override MarshalByRefObject CreateInstance(Type type) {
      var instance = value ?? (value = base.CreateInstance(type));
      return (MarshalByRefObject)instance;
    }
  }
}
Help will always be given at Hogwarts to those who ask for it.
Re: [Этюд] Сломанный деструктор
От: Sinix  
Дата: 21.09.10 08:54
Оценка: 27 (1) +1 :))) :))) :))) :))) :)
Здравствуйте, Mab, Вы писали:

А вот так:
    class DontSayGoodbye
    {
      ~DontSayGoodbye()
      {
        Console.Clear();
        Console.WriteLine("hello");
      }
    }

?
Re[2]: [Этюд] Сломанный деструктор
От: shakm Россия  
Дата: 21.09.10 08:56
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Mab, Вы писали:


S>А вот так:

S>
S>    class DontSayGoodbye
S>    {
S>      ~DontSayGoodbye()
S>      {
S>        Console.Clear();
S>        Console.WriteLine("hello");
S>      }
S>    }
S>

S>?

ржачка
Re[2]: [Этюд] Сломанный деструктор
От: Пельмешко Россия blog
Дата: 21.09.10 09:00
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Здравствуйте, Mab, Вы писали:


Mab>>Как устроен DontSayGoodbye?


_FR>И мои пять копеек:


Всё равно при выгрузке домена отрабатывает
Re[2]: [Этюд] Сломанный деструктор
От: Lloyd Россия  
Дата: 21.09.10 09:02
Оценка: :))) :))
Здравствуйте, Sinix, Вы писали:

S>А вот так:

S>
S>    class DontSayGoodbye
S>    {
S>      ~DontSayGoodbye()
S>      {
S>        Console.Clear();
S>        Console.WriteLine("hello");
S>      }
S>    }
S>

S>?

Да ты шалун.
Re[2]: эээ?
От: Константин Л.  
Дата: 21.09.10 10:37
Оценка:
Здравствуйте, Aznog, Вы писали:

>>> ...

>>> Как устроен DontSayGoodbye?
A>
A>    abstract class DontSayGoodbye
A>    {
A>        protected abstract void Finalize();
A>    }
A>


из-за protected? каков механизм?
Re[3]: эээ?
От: Пельмешко Россия blog
Дата: 21.09.10 10:58
Оценка: 99 (6)
Здравствуйте, Константин Л., Вы писали:

КЛ>Здравствуйте, Aznog, Вы писали:


>>>> ...

>>>> Как устроен DontSayGoodbye?
A>>
A>>    abstract class DontSayGoodbye
A>>    {
A>>        protected abstract void Finalize();
A>>    }
A>>


КЛ>из-за protected? каков механизм?


Дело в том, что компилятор C# при компилировании деструктора должен сгенерировать такой вот код:

protected override void Finalize()
{
    try
    {
        Console.WriteLine("goodbye");
    }
    finally
    {
        base.Finalize();
    }
}

То есть деструктор должен вызвать предудыщие переопределения метода object.Finalize() после своего тела.
Так вот, разработчики компилятора C# при генерации данного кода видимо тупо искали а базовых классах виртуальный метод по имени "Finalize" и генерировали для него base-вызов (который в C# всегда невиртуальный), так что если создать абстрактный метод (или виртуальный, но не override!) с таким именем, то компилятор C# успешно генерирует невиртуальный вызов абстрактного метода:

.method family hidebysig virtual instance void Finalize() cil managed
{
    .maxstack 1
    L_0000: nop 
    L_0001: ldstr "goodbye"
    L_0006: call void [mscorlib]System.Console::WriteLine(string)
    L_000b: nop 
    L_000c: nop 
    L_000d: leave.s L_0017
    L_000f: ldarg.0 
    L_0010: call instance void DontSayGoodbye::Finalize()
    L_0015: nop 
    L_0016: endfinally 
    L_0017: nop 
    L_0018: ret 
    .try L_0000 to L_000f finally handler L_000f to L_0017
}

Такой il код не проходит верификацию peverify,

Microsoft (R) .NET Framework PE Verifier.  Version  4.0.30319.1
Copyright (c) Microsoft Corporation.  All rights reserved.

[IL]: Error: [ConsoleApplication1.exe : Goodbye::Finalize][offset 0x00000010] Call not allowed on abstract methods.
1 Error(s) Verifying bin\Debug\ConsoleApplication1.exe

А в рантайме вызов финализера, видимо, просто молча валится на этапе JIT'а...

Как-то так
Re[4]: эээ?
От: Константин Л.  
Дата: 21.09.10 11:01
Оценка:
Здравствуйте, Пельмешко, Вы писали:

[]

круто
Re[2]: [Этюд] Сломанный деструктор
От: Пельмешко Россия blog
Дата: 21.09.10 11:05
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Здравствуйте, Mab, Вы писали:


Mab>>Как устроен DontSayGoodbye?


_FR>И мои пять копеек:


А вот так точно никогда не вызывает финализер

using System;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;

[DontSayGoodbye]
class DontSayGoodbye : ContextBoundObject
{
    sealed class DontSayGoodbyeRealProxy : RealProxy
    {
        public DontSayGoodbyeRealProxy(Type type) : base(type) { }

        public override IMessage Invoke(IMessage msg)
        {
            var ctorMsg = msg as IConstructionCallMessage;
            if (ctorMsg == null) throw new NotSupportedException();

            var o = InitializeServerObject(ctorMsg);
            GC.SuppressFinalize(GetUnwrappedServer());
            return o;
        }
    }

    sealed class DontSayGoodbyeAttribute : ProxyAttribute
    {
        public override MarshalByRefObject CreateInstance(Type type)
        {
            return (MarshalByRefObject) new DontSayGoodbyeRealProxy(type).GetTransparentProxy();
        }
    }
}

Нет предела извращенству!
Re[3]: эээ?
От: Sinix  
Дата: 21.09.10 11:13
Оценка: 5 (1)
Здравствуйте, Константин Л., Вы писали:

КЛ>из-за protected? каков механизм?

Противоречащий C# Language Specification Version 4.0

10.13 Destructors
...
Destructors are implemented by overriding the virtual method Finalize on System.Object. C# programs are not permitted to override this method or call it (or overrides of it) directly. For instance, the program
class A
{
override protected void Finalize() {} // error
public void F() {
this.Finalize(); // error
}
}
contains two errors.
The compiler behaves as if this method, and overrides of it, do not exist at all. Thus, this program:
class A
{
void Finalize() {} // permitted
}
is valid, and the method shown hides System.Object’s Finalize method.



UPD: Также см ответ выше от Пельмешко.
Re[4]: эээ?
От: Mab Россия http://shade.msu.ru/~mab
Дата: 21.09.10 13:49
Оценка: 174 (8) +1
Здравствуйте, Пельмешко, Вы писали:


П>А в рантайме вызов финализера, видимо, просто молча валится на этапе JIT'а...

Не думаю, что дело в неверифицируемости этого кода. Все остается в силе, даже если написать
class DontSayGoodbye 
{
    protected virtual void Finalize() {}
}


Дело в другом. Объявляя в DontSayGoodbye вирутальный метод с именем Finalize мы заодно скрываем базовый object.Finalize. Убедиться в этом легко, запустив ildasm -- у метода будет стоять флаг newslot. (Кстати, компилятор обычного в таких случаях предупреждения не выдаст, а явно поставленный модификатор new сочтет избыточным.)

Соответственно, переопределение Finalize, которое породит компилятор для Goodbye, будет перекрывать (override) имеенно DontSayGoodbye.Finalzie, а не object.Finalize. Вызов же финализатора, конечно, делается через токен object.Finalize (как самый обычный вирутальный вызов), так что сработает object.Finalize, который пуст.

Итого, после того, как в базовом классе Finalize был скрыт, никаких шансов у производного его переопределить не осталось.

Поправь, если я ошибаюсь.
Re[5]: эээ?
От: Пельмешко Россия blog
Дата: 21.09.10 13:56
Оценка:
Здравствуйте, Mab, Вы писали:

Mab>Поправь, если я ошибаюсь.


Да, я чушь написал, конечно же рантайм вызывать будет по токену object.Finalize()

В Connect сообщили?
Re[6]: эээ?
От: nikov США http://www.linkedin.com/in/nikov
Дата: 21.09.10 14:07
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>В Connect сообщили?


Я отправлял какие-то баги из этой области. Их отказались фиксить ввиду малой важности.
Re[7]: эээ?
От: _FRED_ Черногория
Дата: 21.09.10 14:09
Оценка: +1
Здравствуйте, nikov, Вы писали:

П>>В Connect сообщили?


N>Я отправлял какие-то баги из этой области. Их отказались фиксить ввиду малой важности.


И правильно: важность и правда не так что б уж очень, зато сколько удовольствия общественности!
Help will always be given at Hogwarts to those who ask for it.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.