Как стать автором
Обновить

Комментарии 60

Вот что придумал за 5 минут:

namespace Example
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("before static ctor");
            C c = new C();
            Console.WriteLine("after static ctor");
        }
    }

    class C
    {
        static C()
        {
            Console.WriteLine("from static ctor");
        }

        public C()
        {
        }
    }
}

т. е. создаю и вызываю обычный конструктор, что автоматом вызывает статический
да, такой код скорее всего будет работыть, как и было указано
проблема в том, что С# не гарантирует вызов статического конструктора _при_ первом использовании, а только _до_ первого использования
Этот код работает, проверено. Для большей убедительности можно в конструкторе использовать элементы которые инициализуруются статическим конструктором.
Работать-то будет. Просто, действительно, возможна ситуация, когда статический конструктор отработает в самом начале программы, то есть до первого вызова Console.WriteLine(...)
Я имел в виду, что работать-то будет, но не в 100% случаев.
вы неправильно поняли условие, обычно когда нужно чтобы хотя бы когда-нибудь работало, это описывается специально.
> Тем не менее, напишите код, который гарантированно вызывает указанный выше статический конструктор как раз в нужном месте.

Я подчеркнул на мой взгляд ключевые слова. Статический конструктор вызовется лишь единожды в процессе работы программы. Автор топика просит написать код, который обеспечит его вызов именно в нужном месте. При чём обеспечит гарантированно. Так что «работает, проверенно» это не то, потому что не представлено доказательства, что код всегда будет работать, как требуется по условию задачи.
доказательства ниже, читайте цитаты из книги Джеффри Рихтера
Вот благодаря подобному «работает, проверено» куча программ сыпется при выходе новых версий ОС/фреймворков. Производитель явно написал «гарантируется только вызов до первого использования, а не вовремя» поэтому ваше «работает» — просто совпадение или особенность текущей реализации которую производитель просил не использовать.

Мне сейчас на ум приходят 2 топорных метода:
Мьютекс.
Загрузить тип рефлексией из отдельной сборки.
В таких случаях нужно не гадать на кофейной гуще, а менять постановку задачи чтобы была 100% гарантия правильности исполнения.
надо делать такие решения поставленной задачи, чтобы работало, или доказать, что решить задачу за внятное время и средства невозможно.
Если поведение системы при такой постановке задачи не прогнозируемо, то лучше поменять постановку задачи, чтобы достигнут 100% конечной цели. Это намного лучше чем искать решение, которое может сработать, а может и нет, а также может спровацировать трудно уловимый баг.
задача, как и все такие задачи, представляет собой сферического коня в вакууме, и имеет скорее теоретическую ценность, чем практическую
Вот что пишет Джеффри Рихтер в своей книге «Программирование на платформе Microsoft .NET Framework» стр.160:

Кроме того, CLR ведет себя довольно свободно, принимая решение о вызове кон-
структора типа. CLR вызывает конструктор типа в одном из следующих случаев.
• Прямо перед созданием первого экземпляра типа или перед первым обраще-
нием к полю или члену класса, не унаследованному от предка. Это называется
точной семантикой, поскольку CLR вызывает конструктор типа именно в тот
момент, когда он необходим.
• В любое время перед первым обращением к статическому полю, не унаследо-
ванному от предка. Это семантика с заблаговременной инициализацией поля,
так как CLR гарантирует лишь то, что статический конструктор будет испол-
нен до обращения к статическому полю, возможно, задолго до него.
По умолчанию компиляторы сами выбирают семантику, наиболее подходящую
для вашего типа, и информируют CLR о выборе, устанавливая в метаданных флаг
b e f o r e f i e l d i n i t .


Так что мой пример сработает всегда, так как идет создание конструктора, хотя возножно нужно будет еще добавить вызов какого-нить члена класса для большей гарантии, но это я уже писал выше.
Насколько я понимаю это описание работы текущей версии, а не стандарт. Опираться надо все-таки на стандарт, который гарантировано будет поддержан в следующих версиях.
А вы думаете что текущая версия не поддерживает стандарт? Тогда зачем нужен стандарт, который не поддерживается?
Я видимо неясно выразился. Одно дело описание механизма работы, другое — стандарт. Цитата из книги Рихтера — это описание механизма работы, который вполне может быть изменен в дальнейшем.
Механизм работы всегда основывается на стандарте, иначе получим систему которая будет вести себя неизвестно как
Как было справедливо указано выше — систему работающую неизвестно как получают, когда программисты затачивают программу под конкретную реализацию системных функций, или просто других библиотек при ВНЕЗАПНОМ смене алгоритма в оных. Причем смена алгоритма вполне укладывается в стандарт, который заранее известен.
Да блин! Большая часть программирования сводится к конструированию черных ящиков с заранее определенными входами и выходами, где кроме особых ситуаций нельзя, категорически нельзя рассчитывать на их содержимое!
про мьютекс интересно
указанный мной статический конструктор менять нельзя по условию, но можно добавить инициализатор поля, который будет делать то, что нужно

правда, не знаю, как там будет с потоками в таком случае, не проверял

про отдельную сборку смотрите ответ
habrahabr.ru/blogs/net/113543/#comment_3649608
т.е. направление близкое, но решение у меня другое
да, с синхронизацией у меня не получилось сделать пример, как раз из-за проблем с потоками
делал через ManualResetEvent, который жду в инициализаторе статического поля класса C, и сигналю между двумя Console.WriteLine

но похожое, инициализация типа проходит в том же потоке, и все просто виснет

если получится сделать внятный примем — буду благодарен
Автору очень сложно описать чего он хочет и при этом он ждет что ответ будет простой?
Уважайте читателей, напишите адекватный пример с кодом.
Насколько я знаю, гарантирует. Цитат отсюда:
The laziness of type initializers is only guaranteed by .NET when the type isn't marked with a special flag called beforefieldinit. Unfortunately, the C# compiler (as provided in the .NET 1.1 runtime, at least) marks all types which don't have a static constructor (i.e. a block which looks like a constructor but is marked static) as beforefieldinit
Ну и в дополнение цитата отсюда: www.yoda.arachsys.com/csharp/beforefieldinit.html

If not marked BeforeFieldInit then that type's initializer method is executed at (i.e., is triggered by):
*first access to any static or instance field of that type, or
*first invocation of any static, instance or virtual method of that type
beforefieldinit?
Если я не ошибаюсь:
namespace Etude_7
{
    public class Program
    {
        static Program()
        {
            Console.WriteLine("Before static .ctor");
            C c = new C();
            Console.WriteLine("After static .ctor");
        }

        public static void Main()
        {
            Console.ReadLine();
        }
    }

    public class C
    {
        private static Program _program = new Program();

        static C()
        {
            Console.WriteLine("from static .ctor");
        }
    }
}


Если первым захочет вызваться конструктор Program — всё ок.
Если первым захочет вызваться конструктор C — то перед его вызовом обязательно будут проинициализированны статические переменные, а внутри инициализации мы уже принудительно вызовем конструктор C.

Хотя это грязный хак.
да, здесь вы меня поймали )
я не указал, что «Before static .ctor» вызывается из Main (у меня), поэтому ваше решение проходит
Если Вы получаете тип при помощи typeof — он известен статически, а в таком случае его конструктор может быть вызван сразу после запуска приложения, ещё до Вашего кода.
Может. А тут вызовем еще раз. По условию не просили «вызвать тут в первый раз» :)
Просили гарантированно в нужном месте :)
В таком случае возможна ситуация, когда конструктор не будет вызван первый раз заранее и получится дублирование:
before
from
from
after
Да. Это нарушает условие «код, который гарантированно вызывает указанный выше статический конструктор как раз в нужном месте»?) Мне кажется нет, задача — вызвать, а больше одного раза, вызывалось раньше — это уже несущественно.
Автор знает что именно он ставил в условие… я прочитал так )
Возможно. Подождём автора.
да, этого я тоже не учел
ну тем и интересней решения получаются, о половине случаев я вообще не думал

в моем решении искомый вызов является первым
Способ неплохой, но насколько я помню:
a) вызывать надо Invoke(null, null) иначе ошибка.
б) в итоге он вызовет статический конструктор два раза
Самая очевидная мысль — сгенерить динамически новую сборку и создать объект между вызовами WriteLine(). Или же загрузить уже готовую сборку, как предлагал VenomBlood
направление мыслей очень близкое, но я не использую Reflection.Emit или другие способы генерации динамической сборки
Вот такой вариант:

class C
{
    private static Action s_orderedCtor = null;
    private static bool s_allowCtor = false;
    private static object s_sync = new object();
 
    public static void AllowCtor()
    {
        lock (s_sync)
        {
            if (s_allowCtor)
                return;
            if (s_orderedCtor != null)
                s_orderedCtor();
            s_allowCtor = true;
        }
    }
 
    static C()
    {
        lock (s_sync)
        {
            Action ctor = delegate()
            {
                Console.WriteLine("from static ctor");
            };
            if (s_allowCtor)
                ctor();
            else
                s_orderedCtor = ctor;
        }
    }
 
    public C()
    {
        AllowCtor();
        Console.WriteLine("from non-static ctor");
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("before static ctor");
        C.AllowCtor();
        Console.WriteLine("after static ctor");
        C c = new C();
        c = new C();
        Console.ReadKey();
    }
}


на выходе будет:

before static ctor
from static ctor
after static ctor
from non-static ctor
from non-static ctor
static ctor вызывается до метода C::AllowCtor,
такой пример ничем не отличается от что написал Airog в самом начале
Тело static ctor до вызова AllowCtor не будет вызвано, а запишется в переменную-делегат s_orderedCtor, который уже, в свою очередь, будет вызван при вызове AllowCtor().
если бряк-поинты поставить на входе в static ctor (на строчке lock (s_sync)) и на входе в AllowCtor, на каком поинте сперва встанет?

то что вы наполняете конструктор логикой возможности повторного вхождения, не говорит о том, что он исполняется после. он исполнится и до allow и при вызове оного. просто отработают разные ветви. Исходное условие звучит как «Тем не менее, напишите код, который гарантированно вызывает указанный выше статический конструктор как раз в нужном месте», значит условие не выполнено, конструктор выполнялся ДО.
статический конструктор должен быть именно таким как в условии, т.е. его нельзя менять
namespace caTest
{
class Program
{
public static EventWaitHandle Ewh = new EventWaitHandle(false, EventResetMode.ManualReset);
static void Main(string[] args)
{
Console.WriteLine(«before static ctor»);
Ewh.Set();
C.GoDoSmth();
Console.WriteLine(«after static ctor»);
Console.ReadLine();
}
}
class C
{
static C()
{
Program.Ewh.WaitOne(-1);
Console.WriteLine(«from static ctor»);
}

public static void GoDoSmth()
{
Console.WriteLine(«from static method»);
}
}
}
сорри, чет про форматирование красивое я и забыл :) но зато работает :)
тут хорошо:
habrahabr.ru/blogs/net/113543/#comment_3649325

если не вымораживаться с мьютексами и прочей загрузкой библиотек, то можно забабахать вариант с
полностью ленивой инициализацией через вложенные классы:

using System;
class A
{
class C
{
public static C c = new C();
static C() { Console.WriteLine(«from static ctor»); }
}

static void Main(string[] args)
{
Console.WriteLine(«Before static .ctor»);
C c = C.c;
Console.WriteLine(«After static .ctor»);
Console.In.ReadLine();
}
}
почему свойства статического конструктора вложенного класса должны отличаться от невложенного? не понимаю
никто не говорит, что должны. принципиально решение не отличается от первого приведенного [кстати, правильного, если смотреть на действующую спецификацию ECMA-335]; оно всего-навсего копирует шаблон singleton'а.
ps есть подозрение, что вы путаете инициализацию статических полей [с beforefieldinit над классом], которая действительно недетерминирована, и вызов статического конструктора [без beforefieldinit над классом], который вполне детерминирован.
признаю, вы правы
в этом месте мсдн противоречит спецификации ECMA, и в теории приоритет следует отдать стандарту (хотя для практических целей я бы основывался на теории, у Липперта в блоге есть примеры, когда реализация сознательно противоречит спецификации)

специально для вас сформулирую иначе:
в классе С есть поле:
public static A a = new A();

конструктор А выглядит таким образом:
public A() { Console.WriteLine(«from initializer of static field»); }

вывести эту строку между «before» и «after», как и раньше

статического конструктора в классе С нет (beforefieldinit есть)

класс A приватно определен в классе С (извне недоступен)
отдать предпочтение, разумеется
еще поправочка,
public static object a = new A();

поскольку А приватно определен, наружу отдавать его нельзя
ок.
должно ли решение работать под любым framework?
если нет, то можно через DynamicExpression скомпилировать выражение, в котором есть обращение к полю класса C, и вызвать его.
если без DynamicExpression, то кроме habrahabr.ru/blogs/net/113543/#comment_3649608 быстро ничего не придумывается( может быть вечером подумаю. ну или ваше решение посмотрю.
Может быть так:
Console.WriteLine("before static ctor");
Action action = () => new C();
action(); //call of static ctor
Console.WriteLine("after static ctor");
это все несет тот же смысл, что и самый первый комментарий
тип С известен заранее, а значит, может создаться где угодно
как насчет помещения класса С в отдельную сборку, которая будет загружаться динамически?
извините, не заметил комментариев выше )
internal class Program {
    private static void Main(string[] args) {
        Console.WriteLine("before static ctor");
        var c2 = Factory.Instance;
        Console.WriteLine("after static ctor");
        Console.ReadLine();
    }
}

public static class Factory {
    private static AppDomain domain;
    private static readonly object syncRoot = new object();

    public static object Instance {
        get {
            lock (syncRoot) {
                if (domain != null)
                    AppDomain.Unload(domain);

                domain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
            }

            return domain.CreateInstanceAndUnwrap(
                    "ConsoleApplication1",
                    "ConsoleApplication1.Factory+C");
        }
    }

    private class C : MarshalByRefObject {
        static C() { Console.WriteLine("from static ctor"); }
    }
}

да, это решение конечно будет работать
Похоже, других вариантов уже не будет, поэтому вот мое решение. Удивительно, что никто не предложил.
Поскольку типа С до «before static ctor» в домене просто нет, то и статический конструктор его не может вызваться.
Этот же способ (с дополнением одной строчки) покрывает более правильный случай, указанный habrahabr.ru/blogs/net/113543/#comment_3650548

using System;

class C{
static C() { Console.WriteLine(«from static ctor»); }
}

class App
{
static void Main()
{
Console.WriteLine(«before static ctor»);
var t = typeof(C<>).MakeGenericType(new Type[] { typeof(string) } );
object o = Activator.CreateInstance(t);
Console.WriteLine(«after static ctor»);
}
}
пропали теги, к сожалению

class C<T>{
static C() { Console.WriteLine(«from static ctor»); }
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории