.NET

индекс
121,03

C#: Этюды, часть 2

Продолжение, начало здесь

Начну с соцопроса: какие Вы знаете способы выполнить код до начала функции Main() (надеюсь, что перечислят все известные мне и парочку неизвестных :) )?

А теперь задачка:

Перед Вами небольшой код, выводящий два символа (кстати, проверьте себя: в каком порядке они выведутся?). Необходимо вывести подчеркивание "_" между этими символами.
Конечно, задача была бы тривиальной без ограничений. А они следующие:

  • не определять другой метод Main
  • не использовать идентификатор Console в качестве имени класса, свойства, поля и т.д. (спасибо irc-юзеру Gopneg)
  • не изменять код всех существующих методов: App.Main, X.X, Y.Y
  • не изменять определение поля X.y
  • не добавлять новые поля в класс X


using System;

class Y
{
  public Y()
  {
    Console.Write("0");
  }
}

class X
{
  public X()
  {
    Console.Write("o");
  }
  Y y = new Y();
}

class App
{
  static void Main()
  {
    X x = new X();
  }
}

* This source code was highlighted with Source Code Highlighter.


Желаю удачи!

PS
итак, у нас два победителя: mace был первым и угадал авторское решение: http://habrahabr.ru/blogs/net/77039/#comment_2241079
а хабраюзер bobermaniac предложил способ элегантно обойти моё ограничение на переопределение Console: http://habrahabr.ru/blogs/net/77039/#comment_2241109

Кстати, вопрос о вызове кода до Main всё еще актуален! Пока предложен самый очевидный — статический конструктор класса.

PPS
Внезапно, еще одно решение предложил SHSE: http://habrahabr.ru/blogs/net/77039/#comment_2243183, причем основано оно на совсем других механизмах, но прекрасно работает! )
+9
2 декабря 2009, 14:07
4

комментарии (43)

+1
bobermaniac #
class App { static App() {/* код до Main */} static void Main() {}}

Это то, что сразу пришло в голову.
0
iaroshenko #
да, этот способ годится + есть много его вариаций )
может, еще какой-то знаете?
0
bobermaniac #
Больше никакого не знаю, в том плане, что никогда не задумывался над вопросом, как можно выполнить функцию до Main().

С другой стороны, возможностей должно быть достаточно много. Если покопаться — можно попробовать найти те вещи, которая инфраструктура .NET так или иначе запускает до Main() и привязаться к ним. Но это надо смотреть, чем я сейчас и занимаюсь.
0
mace #
так?
using System;

class Y
{
 public Y()
 {
  Console.Write("0");
 }
}

class X
{
 private X(string s)
 {
  Console.Write(s);
 }

 public X() : this("_")
 {
  Console.Write("o");
 }
 Y y = new Y();
}

class App
{
 static void Main()
 {
  X x = new X();
 }
}


* This source code was highlighted with Source Code Highlighter.
+3
mace #
Или так?
using System;

class Y
{
 public Y()
 {
  Console.Write("0");
 }
}

class Z
{
 public Z()
 {
  Console.Write("_");
 }
}

class X : Z
{
 public X()
 {
  Console.Write("o");
 }
 Y y = new Y();
}

class App
{
 static void Main()
 {
  X x = new X();
 }
}


* This source code was highlighted with Source Code Highlighter.
+1
iaroshenko #
этот вариант правильный, предыдущий — нет, поскольку меняет код конструктора X.X
+3
nullbie #
кстати технически наследование тоже меняет код конструктора — в IL между инициализаторами полей и телом конструктора вставится вызов конструктора базового класса.
0
iaroshenko #
спасибо за ремарку, но под кодом в данном случае (и всех остальных) я подразумеваю исключительно C#-код, если не сказано обратное
0
bobermaniac #
:this("_") — это вызов конструктора, в принципе равноценно изменению кода метода. Насколько я понял, по условию это не допустимо.
0
mace #
да, согласен. потому и написал второй вариант.
0
alek_sys #
А инициализацию Y можно менять? На Y y = new Y("");?
0
iaroshenko #
по условию — нельзя
+3
bobermaniac #
В общем, у меня случился такой код. Он работает и решает именно эту задачу.

using System;
using System.Threading;
using System.IO;

class Y
{
  public Y()
  {
    Console.Write("0");
  }
}

class X
{
  public X()
  {
    Console.Write("o");
  }
  Y y = new Y();
}

class App
{
  public class CustomWriter : TextWriter
  {
    private TextWriter c = null;

    public CustomWriter()
    {
      c = Console.Out;
      Console.SetOut(this);
    }

    public override System.Text.Encoding Encoding
    {
      get { return c.Encoding; }
    }

    public override void Write(object value)
    {
      c.Write(value);
    }

    public override void Write(string value)
    {
      c.Write(value);
      if (value == "0")
        c.Write("_");
    }
  }

  static App()
  {
    Console.SetOut(new CustomWriter());
  }

  static void Main()
  {
    X x = new X();
    Console.ReadLine();
  }
}


* This source code was highlighted with Source Code Highlighter.

0
iaroshenko #
да, можно принять как правильное ))
похоже, я недостаточно наложил ограничений на консольный вывод )
0
bobermaniac #
Да, надо было еще накладывать ограничение на недопустимость перенаправления стандартных потоков ввода-вывода, тогда этот код стал бы невозможен.

Но, в принципе, тут смотря что от задачи требуется. Мой код решает задачу, даже если накладывается ограничение на полную неизменность классов X и Y.
+2
Meroving #
Вы и так уже наложили столько ограничений, что задачка определенно напоминает сферического коня в вакууме. кажется, вам просто пришел в голову некий вариант, а дальше вы усиленно пытаетесь всех к нему склонить, загоняя ограничениями в прокрустово ложе, хотя в таких задачах самое интересное обычно — неожиданный подход, как в посте выше. Примерно так же работают некоторые учителя в школах(и не только в школах), к сожалению.
0
iaroshenko #
насчет сферического коня спорить не буду )
а смысл в такой задачке был в порядке вызова элементов — инициализатор производного типа, конструктор базового типа, конструктор производного типа. мне эта последовательность показалась неочевидной, на ней я и попытался построить задачку. то, что bobermaniac смог мои ограничения обойти — ему только в плюс, я считаю
0
wdk #
Добавляем в конец
class MyApp
{
static void Main()
{
Console.Write(«0_o»);
}
}

и компилим с опцией /main:MyApp
0
iaroshenko #
первое ограничение запрещает определять еще один метод Main ;)
0
wdk #
Проглядел :(
0
mezastel #
А можно мне тоже какой-нибудь «этюд» запостить?
0
iaroshenko #
буду только рад!
0
bobermaniac #
А, кстати, о вызове когда до Main не поведаете?

Еще будет замечательно, если поведаете, как вы это обнаружили и когда такие вызовы бывают нужны.
0
iaroshenko #
вот ниже показали еще способ
ну и применим тот же способ, которым решается этюд
по сути в этом и заключался исходный этюд для канала #c# )
0
bobermaniac #
Ну такой вызов — это дурной тон. Код в конструкторе, да еще и эксепшн если вылетит, перехватить его не удастся. Жуть.
0
iaroshenko #
да, пожалуй
кстати, какого типа будет это неперехваченное исключение? )
0
bobermaniac #
TargetInvocationException, как и любое другое исключение, выкинутое из конструктора.
0
iaroshenko #
MSDN говорит о TargetInvocationException: The exception that is thrown by methods invoked through reflection

Здесь у нас нет рефлексии вроде бы. Давайте сформулирую четче: речь идет о коде habrahabr.ru/blogs/net/77039/#comment_2241555
какое исключение получится в результате, если конструктор Z.Z() бросит, скажем, NullReferenceException?
0
bobermaniac #
В таком случае NullReferenceException и вылетит.
0
iaroshenko #
нет )
NullReferenceException будет обернут внутрь другого исключения
убедитесь сами
0
bobermaniac #
Эмм…

class Test
{
  public Test() { throw new ApplicationException(); }
}

class App
{
  static void Main()
  {
    try
    {
      new Test();
    }
    catch (Exception ex)
    {
      Console.WriteLine(ex.GetType().FullName);
    }
    Console.ReadLine();
  }
}


* This source code was highlighted with Source Code Highlighter.


Выдает System.ApplicationException с полным стеком. Аналогично для NullReferenceException.
0
iaroshenko #
Ваш код радикально отличается от того, что приведен ниже )
смысл в том, что передаваемый там эксепшн нельзя перехватить обычными средствами, и приходится смотреть вывод консоли:
class Test
{
 static Test() { throw new ApplicationException(); }
}

class App
{
 static void Main()
 {
   Test test = new Test();
 }
}


* This source code was highlighted with Source Code Highlighter.

обратите внимание на то, что конструктор статический
0
iaroshenko #
других, к сожалению, не знаю, но очень хочется узнать
0
iaroshenko #
кстати, есть еще один способ — через атрибуты. правда, в мелкософте об этом догадались, и все атрибуты, которые проверяются перед запуском, помечены как sealed (
так что только через подмену сборки со стандартными атрибутами
+1
plsc_Rover #
Вот так вызовется до Main. Правда вставить "_" между Oо не получается пока.
class App
{
public static Z z = new Z();
static void Main()
{
X x = new X();
}
}
0
TIGERoX #
class App
{
[AutoStart()]
static void Main()
{
}
}

public class AutoStartAttribute: Attribute
{
public AutoStartAttribute()
{
Console.Beep();
}
}
0
iaroshenko #
у меня не срабатывает, фреймворк 3.5 SP1
смотрите habrahabr.ru/blogs/net/77039/#comment_2241758
пользовательские атрибуты создаются, когда их запрашивают
можно было бы унаследовать атрибут, который запрашивает сама среда выполнения (InAttribute, STAThreadAttribute), но, похоже, все они sealed
0
TIGERoX #
Кхм, а у меня срабатывает, тоже SP1 3.5. По стеку видно, что internal код вызывает GetCustomAttributes… ну и в результате логичное создание экземпляра нашего атрибута
0
iaroshenko #
любопытно.
покажите тогда полностью код и параметры компилятора
у Вас один файл на вход компилятору подается?
0
TIGERoX #
параметры дефолтные… я просто создал новый консольный проект

v3.5\Csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll /debug+ /debug:full /filealign:512 /optimize- /out:obj\Debug\ConsoleApplication2.exe /target:exe Program.cs Properties\AssemblyInfo.cs
0
iaroshenko #
ага, воспроизвел
такое поведение получается в случае, если пускать проект из-под студии по F5 (в режиме отладки). Попробуйте запустить в обычном режиме (Ctrl + F5), либо прямо из папки bin
я свои примеры кода компилирую обычно из командной строки, потому не сразу понял, в чем дело
0
SHSE #
Ещё одно решение. Только если вставить Console.ReadLine() в Main, то работать не будет. Запускать нагляднее не из VS а из коммандной строки.
class Y {
    public Y() {
      Console.Write("0");
    }
  }

  class X {
    public X() {
      Console.Write("o");
    }
    Y y = new Y();
  }

  class Z {
    ~Z() {
      Console.Write("\r0_o");
    }
  }

  class App {
    static void Main() {
      X x = new X();
    }

    static Z z = new Z();
  }


* This source code was highlighted with Source Code Highlighter.
0
iaroshenko #
да, тоже работает
все-таки ограничения стимулируют фантазию )

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.