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

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

В этом вопросе иголки «скрытых фич» искать очень сложно, они завалены сеном базовых фич и банального синтаксического сахара языка, о которых нормальный разработчик и так должен знать.
Использовать CallerMemberName для реализации INotifyPropertyChanged опасно — работа кода после обфускации не гарантирована.
Глюки из-за обфускации — это скорее повод избегать обфускации, а не избегать вкусных фич.
Слишком толсто :)
Значит не ленимся, открываем почту и пишем производителю обфускатора:

Уважаемый производитель, я в своем коде использую [CallerMemberName], а после обфускации (у меня ничего не работает|у соседа ничего не работает|мне страшно жить). С уважением, Вася Пупкин

Отсылаем, ждем. Если производитель выдал нелепую отписку или не ответил в разумное время, считаем $200 выкинутыми и ищем новый обфускатор. Есть еще правда вариант что снизойдет прозрение и вы поймете что обфускация бессмысленна т.к. толком ничего ни от кого не защищает и те кому надо все равно все отреверсят.
Ну давайте посмотрим. Допустим, существует код:
class Test
{
	public void Main()
	{
		DetectName();
		DetectName("WTF");
	}
	
	public void DetectName([CallerMemberName] string name = null)
	{
		Console.WriteLine("name: {0}", name);
	}
}


Компилятор преображает первый вызов DetectName() в DetectName("Main"), подставляя туда строковую константу. С точки зрения IL вызовы получаются идентичными. Как обфускатор сможет определить, что в первом случае заменить строку необходимо, а во втором нельзя?

Из этого, кстати, вытекает второй недостаток использования CallerMemberName — иногда бывает необходимо вызвать обновление вообще из другого метода. Например, есть несколько get-only свойств и некий метод Refresh, который явно запрашивает обновления — или же несколько обновлений в одном setter'е. Так что пока подход с Expression Tree наподобие notify(() => Property) самый удобный, функциональный и поддерживаемый.

Я согласен, что обфускация, как и любой другой способ, не гарантирует ничего на 100%. Однако во многих случаях она позволяет поднять сложность реверса до достаточного уровня, когда большинство wannabe-хакеров плюнут и перестанут пытаться. По крайней мере, у нас .NET Reactor показал себя очень хорошо и уже больше года взломанных версий нашего продукта в сети не находили.
Следует заметить, что подход с Expression Tree также не лишён недостатков, помимо низкой производительности в сравнении с CallerMemberName, он также подвержен ошибкам при копировании-вставке кода. Например следующий код верен с точки зрения компилятора, но в нем есть ошибка:

protected bool SetValue<T>(ref T property, T value, Expression<Func<T>> propertyDelegate)
{
	if (Object.Equals(property, value))
	{
		return false;
	}
	property = value;
 
	OnPropertyChanged(propertyDelegate);
 
	return true;
}

private string property1;
private string property2;
 
public string Property1
{
	get { return this.property1; }
	set { SetValue(ref this.property1, value, () => Property1); }
}
 
public string Property2
{
	get { return this.property2; }
	set { SetValue(ref this.property2, value, () => Property1); } // Ошибка тут
}


Кстати, есть отличная статья о все возможных способах реализации интерфейса INotifyPropertyChanged.
С CallerMemberName идут ещё CallerLineNumber и CallerFilePath (думаю по названиям понятно для чего).
CallerMemberName очень полезен, спасибо!
А есть ли способ узнать имя класса?
Если не для продакшн-кода, то пожалуйста, все что угодно — StackFrame. Для логирования, например, прекрасно подходит. Еще с помощью этого класса можно делать интересные трюки, например получение EmbeddedResource для конкретного тестового метода без передачи строк в явном виде, т.е. просто GetResourceForThisTest(), если ресурсы именовать по именам классов\методов.
Два важных замечания про форвардинг типов:
1) Этот механизм будет работать только тогда, когда тип перенесён в другую сборку, но пространство имён осталось прежним.
2) Клиентское приложение будет работать без перекомпиляции, но если вы откроете проект в вижуал студии, то всё «сломается» и клиенту надо будет указать на новую зависимость.
TypeForwardedTo почему то не работает в C++/CLI.
Сделал точно такой же пример на C# — работает, на С++ не работает.
Смотрю через рефлектор, вроде все на месте, и в целом все похоже, но почему то не работает.

Может кто нибудь с этим разбирался и сможет подсказать?
Выложите, пожалуйста, оба примера на C# и на C++/CLI куда-нибудь, например, на GitHub, чтобы можно было поробовать оба ваши варианта.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации