среда, 26 марта 2014 г.

Static в .Net - почему иногда это плохо и как с этим быть?

На так давно я писал про статические конструкторы, а сегодня давайте разберемся что это вообще такое - статические объекты и что значит ключевое слово static. В общем, статический объект - это такой объект который в домене приложения существует в единственно экземпляре. А теперь перейдем к частностям.

Начнем с классов. Если вы объявляете класс статическим, то на него автоматически накладываются несколько ограничений:
1) Статический класс может содержать только статические методы.
2) Статический класс автоматически помечается как sealed, и следовательно от него нельзя наследовать
3) Нельзя создавать экземпляры статических классов.

Исходя из всего этого напрашивается вывод что единственные кандидаты в статические классы - это классы которые не должны хранить состояние. То есть классы, методы которого получают что-то на вход и на базе этих данных выдают результат. Как самый показательный пример - класс Math.
И самый правильный вариант использования начиная с .Net 3.0 - создание классов содержащих методы расширения (extension methods). Их разрешено объявлять только в статических классах.

Ну а еще, очень часто очень хочется сделать статическим какой-нибудь глобальный класс конфигурации. Почему такие желания надо подавлять я расскажу дальше.

Итак переходим к самому интересному - статическим методам и статическим полям. Единственное что отличает статические методы от обычных - это то, что они могут обращаться только к статическим полям. И если вы думаете, что раз статический метод один на все приложение, то он потокобезопасен. Совершенно нет. Вам необходимо самостоятельно отслеживать возможные проблемы с потоками. Более того, так как у статического метода контекст выполнения один на все потоки, использование каких-то сложных потокозависимых действий внутри статического метода может привести к очень не очевидным ошибкам. Вот, например, попробуйте повызвать этот метод из нескольких потоков. Оппаньки случится с очень большой долей вероятности.

public static void GiveMeBug(int threadId)
{
  int test=threadId;
  //тут что-то делаем, скажем считаем до миллиона и ждем переключения потока.
  if (test!=threadId)
  {
    //Оппаньки
  }
}

И теперь самое интересное - статические поля. Посмотрите вот на этот кусок кода:
    class Test1
    {
        public static int x = 10;

        public string GetX()
        {
            return x.ToString();
        }

        public void SetX(int newx)
        {
            x = newx;
        }
    }

    Test1 t1 = new Test1();
    Test1.x = 15;
    Console.WriteLine(t1.GetX());
    
    Test1 t2 = new Test1();
    t2.SetX(25);
    Console.WriteLine(t1.GetX());

Вы уже догадались, что в результате будет выведено 15 и 25, хотя явно мы поле t1.x нигде не изменяли? Вот мы и подошли к тому почему иногда - это плохо.

Представьте себе приложение ASP.NET, а точнее то, как себя будет вести статический класс в рамках этого приложения. Домен приложения - один, а пользователей много. И вот вы сделали статический класс конфигурации. После авторизации каждого пользователя класс конфигурации будет заново заполняться информацией одинаковой для всех пользователей.

Как же быть, спросите вы? Если очень нужен именно  статический класс и именно корректно работающий в разных потоках. Тогда выше спасение - атрибут  [ThreadStatic]. Использовать его можно с любым статическим полем, а смысл его в том, что если поле помечено как ThreadStatic, то для каждого потока создается отдельная независимая копия этого поля, доступная только этому потоку.  Вернусь к примеру про статический класс конфигурации:
    static class UserConfig
    {
        [ThreadStatic]
        public static string realName;
        public static int age;
        //и т.п.....
    }

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

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

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