Здравствуйте, я хотел бы вам рассказать о некоторых редко используемых, но весьма полезных атрибутах из мира .NET.
Итак, поговорим о:
InternalsVisibleToAttribute
Зачем нужен? Этот замечательный атрибут является в какой-то мере дальним родственником ключевому слову friend из C++. Он позволяет любой доверенной сборке обращаться к internal типам и internal членам типов данной сборки. Это довольно старый атрибут, он был добавлен ещё в .NET 2.0.
Когда может пригодиться? Например, его можно использовать для написания модульных тестов для internal типов.(Вы ведь по умолчанию создаете классы помеченные как internal sealed, правда?)
Пример: Предположим, у вас есть сборка Domain.dll, которая содержит internal тип DefaultTimeScalingStrategy. Более того, у вас есть сборка с модульными тестами Domain.Tests.dll. Пусть вы хотите добавить модульные тесты для типа DefaultTimeScalingStrategy. Тогда вам достаточно пометить сборку Domain.dll следующим атрибутом:
[assembly: InternalsVisibleTo("Domain.Tests")]
Замечание: Если сборка Domain.Tests.dll имеет строгое имя, тогда придется использовать не только имя сборки, но и её публичный ключ(НЕ токен).
TypeForwardedToAttribute
Зачем нужен? Указывает, что тип был перенесен из одной сборки в другую, и позволяет текущим потребителям типа не заморачиваться использовать его без перекомпиляции клиентов. Говоря в общем, этот атрибут является частью реализации переадресации типов в CLR, о которой можно почитать например здесь.
Когда может пригодиться? Предположим, вы разрабатываете сборку (Crypto.dll v1.0.0.0), которая пользуется спросом. Полагаю, иногда вы переосмысливаете свои решения принятые в прошлом. Например, вы задумали вынести какой-либо тип(BlumBlumShub) в отдельную сборку(PRNG.dll).
У вас есть два пути:
- Просто выпустить новую версию сборки(Crypto.dll v1.1.0.0), которая теперь ссылается на новую сборку(PRNG.dll). Но тогда вашим пользователям придется перекомпилировать ту часть их приложений, которая использует данный тип(BlumBlumShub), так как он был перемещен и CLR понятия не имеет, где его теперь искать.
- Выпустить новую версию сборки(Crypto.dll v1.1.0.0), которая теперь ссылается на новую сборку(PRNG.dll), и дать подсказку CLR с помощью атрибута TypeForwardedToAttribute о новом местонахождении типа.
Пример: Пусть у вас есть сборка(Crypto.dll), из которой вы хотите вынести тип(BlumBlumShub) в другую сборку(PRNG.dll). Тогда вам будет достаточно пометить сборку Crypto следующим образом:
[assembly: TypeForwardedTo(typeof(BlumBlumShub))]
и, собственно, перенести тип.
Замечания:
- У нашего героя существует антагонист — TypeForwardedFromAttribute.
- Реальным примером использования сабжа является сам .NET 4.0: тип Action<,> пал его жертвой
- Интересно, что TypeForwardedFromAttribute не является обычным пользовательским атрибутом(custom attribute), это — псевдо пользовательский атрибут(pseudo custom attribute). Больше о псевдо атрибутах можно почитать здесь.
- Более детальный пример работы сабжа можно найти здесь.
CallerMemberNameAttribute
Зачем нужен? Позволяет получить имя метода, который вызвал ваш код.
Когда может пригодиться? Атрибут может быть полезен в двух случаях:
- Трассировка и отладка кода
- Реализация интерфейса INotifyPropertyChanged
Пример: С помощью следующего нехитрого кода можно избавить себя от магических строк при реализации интерфейса INotifyPropertyChanged, которые изрядно портят кровь при рефакторинге.
Как было раньше:
internal sealed class Star : INotifyPropertyChanged
{
private int _luminosity;
public int Luminosity
{
get { return _luminosity; }
set
{
if (value != _luminosity)
{
_luminosity = value;
OnPropertyChanged("Luminosity");
}
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Как стало сейчас:
internal sealed class Star : INotifyPropertyChanged
{
private int _luminosity;
public int Luminosity
{
get { return _luminosity; }
set
{
if (value != _luminosity)
{
_luminosity = value;
OnPropertyChanged();
}
}
}
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Обратите внимание на определение и вызов метода OnPropertyChanged.
Замечания:
- У сабжа есть один минус: он появился только в .NET 4.5. Его вроде бы можно использовать и в .NET 4.0(при установке определенного патча), но точно не в более ранних версиях.
- У этого атрибута также есть два брата: CallerFilePathAttribute и CallerLineNumberAttribute. Надеюсь, их имена говорят сами за себя.
MethodImplAttribute
Зачем нужен? Этот атрибут определяет особенности реализации того или иного метода. Может он много чего: от указания компилятору того, что код метода не должен быть оптимизирован, до исполнения кода метода только в одном потоке. Всем этим хозяйством можно управлять выбирая правильные битовые флаги из перечисления MethodImplOptions:
- AggressiveInlining
- ForwardRef
- InternalCall
- NoInlining
- NoOptimization
- PreserveSig
- Synchronized
- Unmanaged
Когда его НЕЛЬЗЯ использовать? Признаться честно, я использовал только один флаг из этого перечисления, а именно Synchronized. Но я хотел бы вас предостеречь от его использования. Умный дядька Дж. Рихтер описал в одной своей замечательной книге пример, когда использование этого флага может привести к взаимной блокировке потоков.
Если кратко, то для флага Synchronized и, например, следующего метода:
public void Foo()
{
// Do something
}
компилятор cгенерирует примерно вот такой код:
public void Foo()
{
lock(this)
{
// Do something
}
}
Это может быть чревато в том случае, когда другой поток попытается наложить блокировку на этот же объект this. Поэтому, во избежание эксцессов, накладывайте блокировку на приватное поле ссылочного типа вместо использования Synchronized, и всё у вас будет хорошо.
Пример:
public static class MathUtility
{
[MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
public static int GetFibonacciNumber(int n)
{
return (int)((Math.Pow((1 + Math.Sqrt(5))/2, n) - Math.Pow((1 - Math.Sqrt(5))/2, n))/Math.Sqrt(5));
}
}
Замечание: Атрибут MethodImplAttribute также как и TypeForwardedToAttribute является псевдо атрибутом. Более того, чтобы узнать был ли он применен к методу или нет, существует специальное API, a именно метод GetMethodImplementationFlags класса MethodInfo.
Вот и всё, что я хотел рассказать.
Если у вас возникли какие-либо вопросы, я буду рад ответить на них по мере своих сил и знаний.
Автор: Saladin