private static void HandleFieldConstant(Set <UsedPropertyChain> container, Func <object> constantObjectRetriever, ChildInformation child, TraversalOptions options) { INotifyPropertyChanged notifiable; Func <object> targetRetriever; if (child.Property != null) { //This occurs in case of expression like: //React.To(() => recursive.PropertyA).Set(...); //or with marker methods: //React.To(() => recursive.TrackFields().PropertyA).Set(...); notifiable = child.Property.GetCurrentParent(); targetRetriever = constantObjectRetriever; } else { //This case occurs, when a method is called on a tracked field, e.g.: //React.To(() => recursive.TrackFields().GetInner().PropertyA).Set(...); //The call to GetInner() breaks the chain and there is no tracked child //for the 'recursive' object. However, this is still a viable case; //if recursive is INPC, track all properties on it, otherwise track nothing. Func <object> parentRetriever = constantObjectRetriever; notifiable = parentRetriever() as INotifyPropertyChanged; targetRetriever = parentRetriever; } if (notifiable != null && child.IsTrackable) //property and method children are trackable { //Do not allow fields here, because they cannot be tracked anyhow. //string.Empty here as the property name does not mean all properties //of the associated object will be tracked. It only means that associated //object is stored in a field and changes to that field cannot be tracked, //just because it's not a property. Thus, string.Empty indicates lack //of any property name. var chain = new UsedPropertyChain(notifiable, string.Empty, child.Property, options); chain.TargetRetriever = targetRetriever; container.Add(chain); } //Otherwise, track nothing }
private static void GetUsedProperties(Set <UsedPropertyChain> container, MemberExpression memberExpression, ChildInformation child, TraversalOptions options) { if (memberExpression == null) { return; } MemberTypes memberType = memberExpression.Member.MemberType; if (memberType != MemberTypes.Property) { if (memberType == MemberTypes.Field) { if (options.TrackFields) { var constant = memberExpression.Expression as ConstantExpression; if (constant != null) { HandleFieldConstant(container, memberExpression, child, options); return; } if (IsFieldChain(memberExpression) && child.IsTrackable) { //Occurs with a.b.c.A kind of expression, where //the field chain is the leading part of the //expression and the constant can be extracted from //it. The constant is taken from evaluation of a.b.c HandleFieldConstant(container, memberExpression, child, options); return; } else { //Dead end. It happens, when a field occurs //after a non-constant element, e.g. after //a property reference as in 'A.B.field.C' //'A.B' is not ConstantExpression and therefore, //even with TrackFields on, the field cannot be //tracked any further. However, the preceding //part of the expression can and should be //tracked. //Break the chain (without continuation) GetUsedProperties(container, memberExpression.Expression, ChildInformation.FieldBreak, options); return; } } else { //Break the chain (without continuation) GetUsedProperties(container, memberExpression.Expression, ChildInformation.FieldBreak, options); return; } } throw new InvalidOperationException("Unhandled case. Revise the code"); } var parentMember = memberExpression.Expression as MemberExpression; if (parentMember != null) { UsedSubproperty usedSubProperty = GetContinuationChainLink(memberExpression, child, options); var usedChild = new ChildInformation(usedSubProperty); //Continue the chain GetUsedProperties(container, parentMember, usedChild, options); } else { if (memberExpression.Expression is ParameterExpression) { //Presumably, we are within a nested lambda parameter block. //Skip, just for now. If there's a nested lambda like "c=>c.property" it //is pretty complicated to know when to update. //TODO: return; } var constant = memberExpression.Expression as ConstantExpression; if (constant != null) { var notifiable = constant.Value as INotifyPropertyChanged; if (notifiable != null) { string propertyName = memberExpression.Member.Name; var chain = new UsedPropertyChain(notifiable, propertyName, child.Property, options); chain.TargetRetriever = CompileMemberExpression(memberExpression); container.Add(chain); } else { //Even now add a chain to the container. Fields are not traced, but their //properties can be. This applies only to cases where a the left-hand part //of an expression is a field or a field/constant chain. HandleFieldConstant(container, memberExpression, child, options); } return; } else { UsedSubproperty usedSubProperty = GetContinuationChainLink(memberExpression, child, options); var usedChild = new ChildInformation(usedSubProperty); //This should break the notification chain or continue, if it's marker method. GetUsedProperties(container, memberExpression.Expression, usedChild, options); } } }