Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
foreach и ifforeach если есть for…до огромных проблем при многопоточном использовании
Каким образом проблемы многопоточного использования решает ваш сниппет?В том-то и дело, что сниппет этим не занимается! Это зовётся инкапсуляцией. Все проблемы класса разруливаются самим классом.
Из других потоков звать нотификацию нельзя, и об этом нужно явно заботиться, т.е. не изменять свойства вьюмодела напрямую из другого потока.Вот это крайне некорректное предложение. Идеальный компилятор это не скомпилирует. :)
Во-первых, что такое «другой поток» в контексте INotifyPropertyChanged?
Во-вторых, вы этим предложением на все свойства объекта и члены INotifyPropertyChanged наложили невидимый контракт, запрещающий их вызов из определённых потоков, а также заставляющий PropertyChanged тоже вызываться в каком-то «не другом» потоке. Это нарушение сразу многих принципов объектно-ориентированного программирования. Короче говоря, так делать нельзя.
В-третьих, это искусственное ограничение вашего фреймворка, который почему-то берёт на себя проблемы самого объекта (или инфраструктуры проекта-пользователя). Вот, например, мой концепт вызова PropertyChanged в разных потоках.
Согласен, что не совсем корректно выразился. Расшифрую. Чаще всего мы работаем не со «сферическим конем в вакууме», а с конкретной ситуацией, когда INotifyPropertyChanged используется UI фреймворком для получения информации об изменениях свойств. UI работает в «главном» потоке приложения, его же называют «UI поток».Нет такого глобального понятия как «UI поток». Возможно, в вашем приложении оно есть, но вы пишите AOP фреймворк. Вероятно, в вашем UI тулките есть такое понятие, но не у пользователей вашего AOP фреймворка.
И вызов обработчиков события PropertyChanged из любого другого потока в данной ситуации не есть правильным.Попробую теперь объяснить на пальцах. У вас есть экземпляр INotifyPropertyChanged с единственным свойством: у него есть событие PropertyChanged, на которое можно подписаться и от него отписаться, причём в обработчик при его вызове будут переданы аргументы, присваиваемые переменной определённого типа. Это полный и окончательный список того, что гарантирует вам этот интерфейс. Здесь не говорится ни о каких потоках, пользователь интерфейса о них попросто не имеет представления.
interface IMyIface {
string GetName(); // never null
}Этот интерфейс обладает единственным свойством: наличием метода GetName, возвращающим не-пустое значение типа string (поскольку этот тип нерасширяемый). C# по-умолчанию не позволяет в сигнатуру метода положить информацию о требуемом не-пустом значении, поэтому его можно описать в комментариях.class Impl : IMyIface {
Stream IMyIface.GetName() { return File.Open("file.txt"); } // error
}class Impl : IMyIface {
string IMyIface.GetName() { return null; }
}Это так же нарушение контракта, хотя этот код скомпилируется.Допустим, у меня есть вьюмодел с множеством свойств, пара из которых может устанавливаться как из UI, так и из фонового потока. Причем установка из UI потока делается намного чаще, чем из фонового. Что делать?Решить проблему на уровне свойств, а не АОП фреймворка.
Насчет искусственного ограничения вообще не понял — не вижу проблем написать аспект, который будет диспатчить вызовы. Приведенные выше примеры аспектов — они не встроены во фреймворк, они могут быть написаны и заинжекчены с помощью него, со всеми нюансами, которые вам нужны.Как я говорил, INPC — это не та проблема, которая должна решаться через аспекты. Даже если это аспекты на конкретно этот класс или конкретно это свойство. Гораздо проще, чище и безопаснее написать полную реализацию вручную, задействовав сниппет, если лень писать повторяющиеся строчки.
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Define a Property for SL MVVM</Title>
<Shortcut>propsl</Shortcut>
<Description>Code snippet for a property using INotifyPropertyChanged. Property have comment</Description>
<Author>al</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>myField</ID>
<ToolTip>Field Name</ToolTip>
<Default>myField</Default>
</Literal>
<Literal>
<ID>type</ID>
<ToolTip>Property Type</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>Property Name</ToolTip>
<Default>MyProperty</Default>
</Literal>
<Literal>
<ID>comment</ID>
<ToolTip>Commentary</ToolTip>
<Default>Коментарий свойства</Default>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[
/// <summary>
/// $comment$
/// </summary>
private $type$ _$myField$ = null;
/// <summary>
/// Возвращает и задает свойство: $comment$
/// </summary>
public $type$ $property$
{
get
{
return _$myField$;
}
set
{
if (_$myField$ != value)
{
_$myField$ = value;
RaisePropertyChanged("$property$");
}
}
}
$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
object OnCall(Action<object[], object> originalMethod, object[] params, SomeContext ctx);originalMethod(params), залогировать/модифицировать результат, обернуть вызов в try-except, вызвать метод несколько раз.object[] paramsFunc<object[], object> конечно, а не Action[Aspect(typeof(SoapExceptionConvertorAspect))][SoapExceptionConvertor][Transaction(500)][Transaction(Timeout = 500, Isolation = Levels.Snapshot)]OnCall(..., new TransactionAttribute(500), ...);OnCall(..., new TransactionAttribute { Timeout = 500, Isolation = Levels.Snapshot }, ...); [Transaction(500)]
[SalesConnection]
[SalaryConnection(true)]
string SomeMethod(int a, int b) string SomeMethod(int a, int b)
{
// созданная обёртка содержит:
var attrs = new WrapperParameter[]
{
new TransactionAttribute(500),
new SalesConnectionAttribute(),
new SalaryConnectionAttribute(true),
};
return (string)TransactionAttribute.OnCall(originalBody, this, new object[] { a, b }, attrs, methodStaticInfo);
}
public class AspectAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)]
public class WrapperParameter : AspectAttribute
{
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)]
public class MethodWrapperAttribute : WrapperParameter
{
public static object OnCall(Func<object[], object> originalMethod, object thisClass, object[] originalMethodParams,
WrapperParameter[] Attrs, OriginalMethodStaticInfo methodInfo) { return null; }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class LinkToWrapperAttribute : AspectAttribute
{
public LinkToWrapperAttribute(Type wrapperType) { }
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)]
public class TransactionAttribute : MethodWrapperAttribute
{
// 'new' позволяет понять, что сигнатура совпала с эталоном в предке.
// не совпала - решарпер слово 'new' выделяет как избыточное
public static new object OnCall(Func<object[], object> originalMethod, object thisClass, object[] originalMethodParams, WrapperParameter[] attrParams, OriginalMethodStaticInfo methodInfo)
{
throw new NotImplementedException();
}
public TransactionAttribute()
{
}
public TransactionAttribute(int timeout)
{
Timeout = timeout;
}
internal int Timeout { get; set; }
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
[LinkToWrapper(typeof(TransactionAttribute))]
public abstract class BaseConnectionAttribute : WrapperParameter
{
public abstract IConnection GetConnection();
}
public class SalesConnectionAttribute : BaseConnectionAttribute
{
public override IConnection GetConnection()
{
throw new NotImplementedException();
}
}
public class SalaryConnectionAttribute : BaseConnectionAttribute
{
public SalaryConnectionAttribute(bool IsReadOnly)
{
}
public override IConnection GetConnection()
{
throw new NotImplementedException();
}
} public class WareVM : INotifyPropertyChanged
{
private decimal _qty;
private decimal _price;
public decimal Qty
{
get { return _qty; }
set
{
if (value == _qty) return;
_qty = value;
OnPropertyChanged();
OnPropertyChanged("Sum");
}
}
public decimal Price
{
get { return _price; }
set
{
if (value == _price) return;
_price = value;
OnPropertyChanged();
OnPropertyChanged("Sum");
}
}
public decimal Sum { get { return Qty*Price; } }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
} [ObservableObject]
public class WareVM
{
public decimal Qty { get; set; }
public decimal Price { get; set; }
[DependOn("Qty", "Price")]
public decimal Sum { get { return Qty*Price; } }
} [ObservableObject]
public class WareVM
{
[AlsoFires("Sum")]
public decimal Qty { get; set; }
[AlsoFires("Sum")]
public decimal Price { get; set; }
public decimal Sum { get { return Qty*Price; } }
}Скорее всего нужно будет копировать тело функции под другим именем (фактически переименовать исходную функцию), а вместо тела исходной добавить тело адвайса
Yet another AOP in .NET