Реклама
Комментарии 35
Ну, я бы не сказал, что проверка на null уж сильно замедляет работу. А вот то, что сборщику мусора не создают дополнительных хлопот — это, конечно, плюс.
Ну и да, меньше геморроя с вызовом — не надо лезть в виртуальную таблицу объекта, чтобы узнать, что именно таки вызывать.
По этой информации разница вызова в цифрах:
— class — 87: Вызов метода из класса безнаследования;
— interface — 929: Вызов через интерфейс;
— abstract — 668: Вызов из наследника абстрактного класса;
— virtual — 669: Вызов виртуального метода;
— static — 83: Вызов статичного метода.

Выигрыш есть, но небольшой.
Пусть есть класс Category со свойством ID, у которого есть метод GetChildCategories.
Ты имеете ввиду, что лучше сделать статический метод GetChaildCategoriesByID(int id) у класса Category, чем простой метод GetChildCategories(), который будет использовать ID категории, для которой этот метод вызывается, я правильно понял?
Тут без разницы, все равно будет вызов типа

var children = Category.GetChildCategoriesById(myCategory.ID);

что так, что эдак будет обращение к инстансу класса.

Можно сделать оба метода, как у File, Directory и прочих.
Один будет сводиться к вызову другого :)
Тут уже от приложения зависит. Часто бывают случаи, когда нужно получить дочерние элементы без самого родительского. Тут уж статический метод не помешает. Хотя делать по другому уже извращение будет.
Нужно будет посмотреть свои коды, какие методы из них можно пометить статическими, подумать. А то что-то сходу не могу придумать в голове удачный пример, если у кого рабочий есть, выкладывайте :) Хотя первым нужно ждать такой от XaocCPS, тебе же решарпер предложил. :)

А по поводу сабжа в целом, думаю это вполне нормальная практика.
Ну, тут как раз имеет смысл делать оба метода.

А примеры, когда можно смело использовать статику:
1. static Image ResizeImage(Image source, Size newSize)
2. static void ThrowException(int ResourceStringId)
Сюда же некоторые файловые методы: DeleteFile(string path), CopyFile(string source, string dest).
Сюда же обертки для PInvoke-вызовов.
Еще не совсем из этой оперы, но статические методы для вызова непубличных конструкторов.
Второй раз вижу вашу статью и второй раз ожидаю хардкор откровений, вероятно, имея ввиду вместо R# не решарпер, а попытку добавить в C# средства метапрограммирования ребят с rsdn.ru.
Аналогично, но RSDN-овский R# помер, когда авторы взялись за Nemerle.
Избежать виртуальных вызовов помогает также закрытие класса (sealed).
Это зарубает дальнейшее наследование, но не означает что не работает уже созданное. И методы всё равно работает с объектами (а не классами). Так что по эффективности это не совсем то, что есть статик.
Правильно заметил предыдущий оратор — не очень удачно вы используете вот это Versus. Вы его используете в значении -против. Но значение -против в этом слове имеет контекст -в сравнении с…

В итоге (по сути) ваш заголовок звучит как Си-шарп в сравнении с решарпер. А как их можно сравнивать если это по своей природе разные вещи?

И потом — статические функции это базовое понятие ООП. Никакого отношения к решарперу они не имеют.
Вы наверно хотели сказать статические методы…
Название действительно не очень удачное для цикла статей, но сама суть очень интересна.
Я не очень давно пользуюсь этим аддоном, и часто например задавался вопросом, чем лучше обьявлять некоторые свои методы статическими, как советовал решарпер. Вот об этом то автор и пишет. Теперь все стало на свои места, спасибо.
Простите, но о какой виртуальности идёт речь? C# — это вам не Java, здесь если метод не помечен как virtual, то он и не будет виртуальным, и, чтобы узнать, какой метод вызывать, ни в какую таблицу лезть не надо. В статический метод просто не передаётся ссылка на объект, и, соответственно, она не проверяется на равенство null-у.
Методы, помеченные как static, вызывается без проверки на существование инстанса класса методом call.
Обычные и виртуальные методы вызываются при помощи callvirt. Если не ошибаюсь, для вызова обычных инстанс методов компилятор обращается в instance v-table.
Нет там никакой особой проверки на «существование инстанса класса». «Object reference not set to an instance of an object.» — это просто новое название для «0xC0000005: Access violation reading location 0x00000000.»

«Обычные» методы по механизму вызова равны статическим, никакого обращения к таблице виртуальных методов нет.
Посмотрим пример.
Обычное консольное приложение:

class Program {
static void Main(string[] args) {
Test.StaticMethod();
Test test = new Test();
test.NonVirtualMethod();
test.VirtualMethod();

}
}

public class Test {
public void NonVirtualMethod() {}
public virtual void VirtualMethod() {}
public static void StaticMethod() {}
}

Компилируем и смотрим ILdasm'ом что получилось:

IL_0001: call void ConsoleApplication2.Test::StaticMethod()
IL_0006: nop
IL_0007: newobj instance void ConsoleApplication2.Test::.ctor()
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: callvirt instance void ConsoleApplication2.Test::NonVirtualMethod()
IL_0013: nop
IL_0014: ldloc.0
IL_0015: callvirt instance void ConsoleApplication2.Test::VirtualMethod()
Однако, из самого Test-а его невиртуальные instance-методы вызываются обычным call-ом:

public class Test
{
    public void NonVirtualMethod() { }
    public virtual void VirtualMethod()
    {
        NonVirtualMethod();
    }
    public static void StaticMethod() { }
}


получаем

.method public hidebysig newslot virtual 
        instance void  VirtualMethod() cil managed
{
  // Code size       9 (0x9)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  call       instance void testvirt.Program/Test::NonVirtualMethod()
  IL_0007:  nop
  IL_0008:  ret
} // end of method Test::VirtualMethod
Хотя да, я понял, в чем фишка.

Компилятор не заморачивается и делает callvirt для вызовов, в которых точно не известно, что вызывать, без обращения к самому объекту. И речь не только о виртуальных, но также и о перекрывающих (new) методах.
По-хорошему там можно написать call instance void ConsoleApplication2.Test::NonVirtualMethod() и все будет работать. Можете проверить, собрав это ilasm-ом обратно :)
А потом IL-код превращается в:

instance = call FFFFFFFFFFEC93E0
static = call FFFFFFFFFFEC93F0
virtual = call qword ptr [rax+60h]

Отсюда видно, что для невиртульных членов обращения «в instance v-table» нет.

Кстати, на счёт лишней «проверки» на null, вы оказались правы, она действительно есть, но только в том случае, когда член не обращается к полям объекта. Т. е. джитер действительно на 100% следует стандарту, и код кидает исключение даже при отсутствии обращений к полям по null-ссылке.
Ни автор, ни комментаторы ещё не озвучили главного недостатка static методов — невозможность модульного тестирования.
Мы не сможем протестировать компонент, если он использует логику другого компонента, который состоит из статичных методов либо сам статичный.
Попробую.
Например, есть BCUser — бизнес компонент для работы с пользователями.
Есть DAUser — компонент для работы с пользователями на уровне базы данных (добавление, удаление и т. д.).

Вы пишите метод в компоненте BCUser, который добавляет пользователя в систему (например по такому алгоритму):
— происходит валидация всех данных;
— происходит обращение к DAUser для сохранения пользователя в базу (у DAUser метод AddUser статичный);
— метод возвращает результат операции.

Чтобы убедиться в корректности работы метода, вам нужно его протестировать (я подразумеваю модульное тестирование). Для этого вам необходимо все вызовы сторонних компонентов заменить на обращение к мокам (Mock-объекты — тестировочный паттерн, суть которого состоит в замене объектов, используемых тестируемым кодом, на отладочные эквиваленты.) Тем самым вы изолируете вашу логику от другой.

В моём примере вам нужно заменить вызов статичного метода компонента DAUser.AddUser() на вызов метода мока. Как это обычно делается:
— используя общий интерфейс (static методы через интерфейс нельзя имплементировать);
— используя наследование (static методы нельзя перекрыть).

Получается, что мы не можем переопределить логику static метода для того, чтобы изолировать наш компонент при тестировании, и наш тест уже будет функциональным (будет использован реальный метод DAUser, который может использовать ещё множество вызовов), что может в дальнейшем породить множество проблем (например, тест будет падать, если мы в цепочке вызовов изменим логику, либо будет отсутствовать доступ к бд на этапе прогона тестов).
Имхо, все зависит от ситуации. На мой взгляд правильнее сделать удобный API использования класса, а не пытаться выиграть в производительности на переделке методов в static. Любое лишнее обращение к БД сразу делает смешными такие попытки улучшения перфоманса.

Лично я чаще всего игнорирую подобные предложения решарпера. Исключение составляют только private хелпер методы, хотя в их случае стоит подумать и возможно вынести их в общий статичный хелпер класс — вдруг где еще этот метод нужен.
Точно, обычно намного удобнее выносить лишнее в хэлпер-класс, потому что сначала у тебя один такой метод, а потом может понадобиться добавить еще и еще, и в итоге получается куча мусора )
Все хорошо, только это знает любой программист, который не торчит носом из-за барной стойки… Зачем такая статья???
Вы не поверите, но даже человек, работающий в солидной фирме сеньёр девелопером 3 года с технологиями .net и asp.net, умудряется путать жизненный цикл страницы на сервере (asp.net) с клиентским в браузере. А вы говорите про статик методы.
> Все хорошо, только это знает любой программист, который не торчит носом из-за барной стойки… Зачем такая статья???

+1

> даже человек, работающий в солидной фирме сеньёр девелопером 3 года с технологиями .net и asp.net, умудряется путать жизненный цикл страницы на сервере (asp.net) с клиентским в браузере

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