Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Base / System / Windows / Freezable.cs / 1 / Freezable.cs
//---------------------------------------------------------------------------- // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // // Description: The Freezable class (plus the FreezableHelper class) // encompasses all of the Freezable pattern. // // See spec at http://avalon/medialayer/Shared%20Documents/Freezables.doc // // History: // 05/01/2003 : [....] - Created // 07/11/2003 : [....] - Removed subsequent history. See SourceDepot. // 08/05/2005 : t-kuberg - Added context information. // //--------------------------------------------------------------------------- using System; using System.Diagnostics; using System.Collections; using System.Collections.Specialized; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using System.Windows.Threading; using MS.Internal; // for Invariant using MS.Internal.WindowsBase; // FriendAccessAllowed using MS.Utility; // FrugalList namespace System.Windows { ////// The Freezable class encapsulates the Freezable pattern for DOs whose /// values can potentially be frozen. See the Freezable documentation for /// more details. /// public abstract class Freezable : DependencyObject, ISealable { #if DEBUG private static int _nextID = 1; private readonly int DebugID = _nextID++; #endif #region Protected Constructors //----------------------------------------------------- // // Protected constructors // //----------------------------------------------------- ////// Construct a mutable Freezable. /// protected Freezable() { Debug.Assert(!Freezable_Frozen && !Freezable_HasMultipleInheritanceContexts && !(HasHandlers || HasContextInformation), "Initial state is incorrect"); } #endregion #region Public Methods //------------------------------------------------------ // // Public methods // //----------------------------------------------------- ////// Makes a mutable deep base value clone of this Freezable. /// /// Caveat: Frozen default values will still be frozen afterwards /// ///A clone of the Freezable. public Freezable Clone() { ReadPreamble(); Freezable clone = CreateInstance(); clone.CloneCore(this); Debug_VerifyCloneCommon(/* original = */ this, /* clone = */ clone, /* isDeepClone = */ true); return clone; } ////// Makes a mutable current value clone of this Freezable. /// /// Caveat: Frozen default values will still be frozen afterwards /// ////// Returns a mutable deep copy of this Freezable that represents /// its current state. /// public Freezable CloneCurrentValue() { ReadPreamble(); Freezable clone = CreateInstance(); clone.CloneCurrentValueCore(this); // Freezable implementers who override CloneCurrentValueCore must ensure that // on creation the copy is not frozen. Debug_VerifyCloneCommon checks for this, // among other things. Debug_VerifyCloneCommon(/* original = */ this, /* clone = */ clone, /* isDeepClone = */ true); return clone; } ////// Semantically equivalent to Freezable.Clone().Freeze() except that /// GetAsFrozen avoids a copying any portions of the Freezable graph /// which are already frozen. /// public Freezable GetAsFrozen() { ReadPreamble(); if (IsFrozenInternal) { return this; } Freezable clone = CreateInstance(); clone.GetAsFrozenCore(this); Debug_VerifyCloneCommon(/* original = */ this, /* clone = */ clone, /* isDeepClone = */ false); clone.Freeze(); return clone; } ////// Semantically equivalent to Freezable.CloneCurrentValue().Freeze() except that /// GetCurrentValueAsFrozen avoids a copying any portions of the Freezable graph /// which are already frozen. /// public Freezable GetCurrentValueAsFrozen() { ReadPreamble(); if (IsFrozenInternal) { return this; } Freezable clone = CreateInstance(); clone.GetCurrentValueAsFrozenCore(this); Debug_VerifyCloneCommon(/* original = */ this, /* clone = */ clone, /* isDeepClone = */ false); clone.Freeze(); return clone; } ////// True if this Freezable can be frozen (by calling Freeze()) /// public bool CanFreeze { get { return IsFrozenInternal || FreezeCore(/* isChecking = */ true); } } ////// Does an in-place modification to make the object frozen. It is legal to /// call this on values that are already frozen. /// ///This exception /// will be thrown if this Freezable can't be frozen. Use /// the CanFreeze property to detect this in advance. public void Freeze() { // Check up front that the operation will succeed before we begin. if (!CanFreeze) { throw new InvalidOperationException(SR.Get(SRID.Freezable_CantFreeze)); } Freeze(/* isChecking = */ false); } #endregion #region Public Properties //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ ////// Returns whether or not the Freezable is modifiable. Attempts /// to set properties on an IsFrozen value result /// in exceptions being raised. /// public bool IsFrozen { get { ReadPreamble(); return IsFrozenInternal; } } internal bool IsFrozenInternal { get { return Freezable_Frozen; } } #endregion #region Public Events //----------------------------------------------------- // // Public Events // //------------------------------------------------------ ////// The Changed event is raised whenever something on this /// Freezable is modified. Note that it is illegal to /// add or remove event handlers from a value with /// IsFrozen. /// ////// An attempt was made to modify the Changed handler of /// a value with IsFrozen == true. /// public event EventHandler Changed { add { WritePreamble(); if (value != null) { ChangedInternal += value; } } remove { WritePreamble(); if (value != null) { ChangedInternal -= value; } } } internal event EventHandler ChangedInternal { add { HandlerAdd(value); // Adding/Removing Changed handlers does not raise the Changed event. // Therefore we intentionally do not call WritePostscript(). } remove { HandlerRemove(value); // Adding/Removing Changed handlers does not raise the Changed event. // Therefore we intentionally do not call WritePostscript(). } } #endregion #region Protected Methods //----------------------------------------------------- // // Protected methods // //----------------------------------------------------- ////// Override OnPropertyChanged so that we can fire the Freezable's Changed /// handler in response to a DP changing. /// protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); // The property system will call us back when a SetValue is performed // on a Freezable. The Freezable then walks it's contexts and causes // a subproperty invalidation on each context and fires any changed // handlers that have been registered. // When a default value is being promoted to a local value the sub property // change that caused the promotion is being merged with the value promotion // change. This fix was implemented for DevDivBug#108642. It is required to // detect this case specially and propagate subproperty invalidations for it. if (!e.IsASubPropertyChange || e.OperationType == OperationType.ChangeMutableDefaultValue) { WritePostscript(); } // OnPropertyChanged is called after the old inheritance context is // removed, but before the new one is added. Debug_DetectContextLeaks(); } ////// Create a default instance of a Freezable object. Actual allocation /// will occur in CreateInstanceCore. /// ///A new instance of the class protected Freezable CreateInstance() { Freezable newFreezable = CreateInstanceCore(); Debug_VerifyInstance("CreateInstance", this, newFreezable); return newFreezable; } // ////// Subclasses must implement this to create instances of themselves. /// See the Freezable documentation for examples. /// ///A new instance of the class protected abstract Freezable CreateInstanceCore(); ////// If you derive from Freezable you may need to override this method. Reasons /// to override include: /// 1) Your subclass has data that is not exposed via DPs /// 2) Your subclass has to perform extra work during construction. For /// example, your subclass implements ISupportInitialize. /// /// The default implementation makes deep clones of all writable, locally set /// properties including expressions. The property's base value is copied -- not the /// current value. It skips read only DPs. /// /// If you do override this method, you MUST call the base implementation. /// /// This is called by Clone(). /// /// The Freezable to clone information from protected virtual void CloneCore(Freezable sourceFreezable) { CloneCoreCommon(sourceFreezable, /* useCurrentValue = */ false, /* cloneFrozenValues = */ true); } ////// If you derive from Freezable you may need to override this method. Reasons /// to override include: /// 1) Your subclass has data that is not exposed via DPs /// 2) Your subclass has to perform extra work during construction. For /// example, your subclass implements ISupportInitialize. /// /// The default implementation goes through all DPs making copies of their /// current values. It skips read only and default DPs /// /// If you do override this method, you MUST call the base implementation. /// /// This is called by CloneCurrentValue(). /// /// The Freezable to copy info from protected virtual void CloneCurrentValueCore(Freezable sourceFreezable) { CloneCoreCommon(sourceFreezable, /* useCurrentValue = */ true, /* cloneFrozenValues = */ true); } ////// If you derive from Freezable you may need to override this method. Reasons /// to override include: /// 1) Your subclass has data that is not exposed via DPs /// 2) Your subclass has to perform extra work during construction. For /// example, your subclass implements ISupportInitialize. /// /// The default implementation makes clones of all writable, unfrozen, locally set /// properties including expressions. The property's base value is copied -- not the /// current value. It skips read only DPs and any values which are already frozen. /// /// If you do override this method, you MUST call the base implementation. /// /// You do not need to Freeze values as they are copied. The result will be /// frozen by GetAsFrozen() before being returned. /// /// This is called by GetAsFrozen(). /// /// The Freezable to clone information from protected virtual void GetAsFrozenCore(Freezable sourceFreezable) { CloneCoreCommon(sourceFreezable, /* useCurrentValue = */ false, /* cloneFrozenValues = */ false); } ////// If you derive from Freezable you may need to override this method. Reasons /// to override include: /// 1) Your subclass has data that is not exposed via DPs /// 2) Your subclass has to perform extra work during construction. For /// example, your subclass implements ISupportInitialize. /// /// The default implementation goes through all DPs making copies of their /// current values. It skips read only DPs and any values which are already frozen. /// /// If you do override this method, you MUST call the base implementation. /// /// You do not need to Freeze values as they are copied. The result will be /// frozen by GetCurrentValueAsFrozen() before being returned. /// /// This is called by GetCurrentValueAsFrozen(). /// /// The Freezable to clone information from protected virtual void GetCurrentValueAsFrozenCore(Freezable sourceFreezable) { CloneCoreCommon(sourceFreezable, /* useCurrentValue = */ true, /* cloneFrozenValues = */ false); } ////// If you derive from Freezable you will need to override this if your subclass /// has data that is not exposed via DPs. /// /// The default implementation goes through all DPs and returns false /// if any DP has an expression or if any Freezable DP cannot freeze. /// /// If you do override this method, you MUST call the base implementation. /// /// This is called by Freeze(). /// /// If this is true, the method will just check /// to see that the object can be frozen, but won't actually freeze it. /// ///True if the Freezable is or can be frozen. protected virtual bool FreezeCore(bool isChecking) { EffectiveValueEntry[] effectiveValues = EffectiveValues; uint numEffectiveValues = EffectiveValuesCount; // Loop through all DPs and call their FreezeValueCallback. for (uint i = 0; i < numEffectiveValues; i++) { DependencyProperty dp = DependencyProperty.RegisteredPropertyList.List[effectiveValues[i].PropertyIndex]; if (dp != null) { EntryIndex entryIndex = new EntryIndex(i); PropertyMetadata metadata = dp.GetMetadata(DependencyObjectType); FreezeValueCallback freezeValueCallback = metadata.FreezeValueCallback; if(!freezeValueCallback(this, dp, entryIndex, metadata, isChecking)) { return false; } } } return true; } // // _eventStorage is used as a performance/memory speedup when firing change handlers. // It exists once per thread for thread safety, and is used to store the list of change // handlers that are gathered by GetChangeHandlersAndInvalidateSubProperties. Reusing the // same EventStorage gives gains because it doesn't need to be reallocated each time // FireChanged occurs. // [ThreadStatic] static private EventStorage _eventStorage = null; ////// Property to access and intialize the thread static _eventStorage variable. /// private EventStorage CachedEventStorage { get { // make sure _eventStorage is not null - with ThreadStatic it appears that the second // thread to access the variable will set this to null if (_eventStorage == null) { _eventStorage = new EventStorage(INITIAL_EVENTSTORAGE_SIZE); } return _eventStorage; } } ////// Gets an EventStorage object to be used to cache event handlers and sets it to be /// in use. /// ////// An EventStorage object to be used to cache event handlers that is set /// to be in use. /// private EventStorage GetEventStorage() { EventStorage eventStorage = CachedEventStorage; // if we reach a case where EventStorage is being used - meaning FireChanged called // a handler that in turn called FireChanged which is probably a bad thing to have // happen - just allocate a new one that won't be cached. if (eventStorage.InUse) { // use the cached EventStorage's physical size as an estimate of how big we // need to be in order to avoid growing the newly created EventStorage int cachedPhysicalSize = eventStorage.PhysicalSize; eventStorage = new EventStorage(cachedPhysicalSize); } eventStorage.InUse = true; return eventStorage; } ////// This method is called when a modification happens to the Freezable object. /// protected virtual void OnChanged() { } ////// This method walks up the context graph recursively, gathering all change handlers that /// exist at or above the current node, placing them in calledHandlers. While /// performing the walk it will also call OnChanged and InvalidateSubProperty on all /// DO/DP pairs encountered on the walk. /// private void GetChangeHandlersAndInvalidateSubProperties(ref EventStorage calledHandlers) { this.OnChanged(); Freezable contextAsFreezable; if (Freezable_UsingSingletonContext) { DependencyObject context = SingletonContext; contextAsFreezable = context as Freezable; if (contextAsFreezable != null) { contextAsFreezable.GetChangeHandlersAndInvalidateSubProperties(ref calledHandlers); } if (SingletonContextProperty != null) { context.InvalidateSubProperty(SingletonContextProperty); } } else if (Freezable_UsingContextList) { FrugalObjectListcontextList = ContextList; DependencyObject lastDO = null; int deadRefs = 0; for (int i = 0, count = contextList.Count; i < count; i++) { FreezableContextPair currentContext = contextList[i]; DependencyObject currentDO = (DependencyObject)currentContext.Owner.Target; if (currentDO != null) { // we only want to grab change handlers once per context reference - so skip // until we find a new one if (currentDO != lastDO) { contextAsFreezable = currentDO as Freezable; if (contextAsFreezable != null) { contextAsFreezable.GetChangeHandlersAndInvalidateSubProperties(ref calledHandlers); } lastDO = currentDO; } if (currentContext.Property != null) { currentDO.InvalidateSubProperty(currentContext.Property); } } else { ++deadRefs; } } PruneContexts(contextList, deadRefs); } GetHandlers(ref calledHandlers); } /// /// Extenders of Freezable must call this method at the beginning of any /// public API which reads the state of the object. (e.g., a proprety getter.) /// This ensures that the object is being accessed from a valid thread. /// protected void ReadPreamble() { VerifyAccess(); } ////// Extenders of Freezable must call this method prior to changing the state /// of the object (e.g. the beginning of a property setter.) This ensures that /// the object is not frozen and is being accessed from a valid thread. /// protected void WritePreamble() { VerifyAccess(); if (IsFrozenInternal) { throw new InvalidOperationException( SR.Get(SRID.Freezable_CantBeFrozen,GetType().FullName)); } } ////// Extenders of Freezable must call this method at the end of an API which /// changed the state of the object (e.g., at the end of a property setter) to /// raise the Changed event. Multiple state changes within a method or /// property may be "batched" into a single call to WritePostscript(). /// protected void WritePostscript() { FireChanged(); } ////// Extenders of Freezable call this to set in a new value for internal /// properties or other embedded values that themselves are DependencyObjects. /// This method insures that the appropriate context pointers are set up for /// the old and the new Dependency objects. /// /// In this version the property is set to be null since /// it is not explicitly specified. /// /// /// The previous value of the property. /// The new value to set into the property protected void OnFreezablePropertyChanged( DependencyObject oldValue, DependencyObject newValue ) { OnFreezablePropertyChanged(oldValue, newValue, null); } ////// Extenders of Freezable call this to set in a new value for internal /// properties or other embedded values that themselves are DependencyObjects. /// This method insures that the appropriate context pointers are set up for /// the old and the new DependencyObject objects. /// /// The previous value of the property. /// The new value to set into the property /// The property that is being changed or null if none protected void OnFreezablePropertyChanged( DependencyObject oldValue, DependencyObject newValue, DependencyProperty property ) { // NTRAID#Longhorn-1023842 -4/27/2005-[....] // // We should ensure dispatchers are consistent *before* modifying // changed handlers, otherwise we will leave the freezable in an // inconsistent state. // if (newValue != null) { EnsureConsistentDispatchers(this, newValue); } if (oldValue != null) { RemoveSelfAsInheritanceContext(oldValue, property); } if (newValue != null) { ProvideSelfAsInheritanceContext(newValue, property); } } ////// Helper method that just invokes Freeze on provided /// Freezable if it's not null. Otherwise it doesn't do anything. /// /// Freezable to freeze. /// If this is true, the method will just check /// to see that the object can be frozen, but won't actually freeze it. /// ///True if the Freezable was or can be frozen. /// False if isChecking was true and the Freezable can't be frozen. /// ///This exception /// will be thrown if isChecking is passed in as false and this /// Freezable can't be frozen. // static protected internal bool Freeze(Freezable freezable, bool isChecking) { if (freezable != null) { return freezable.Freeze(isChecking); } //I guess something that's null is always frozen. return true; } #endregion // Protected Methods #region ISealable /// /// Can this freezable be sealed /// bool ISealable.CanSeal { get { return CanFreeze; } } ////// Is this freezable sealed /// bool ISealable.IsSealed { get { return IsFrozen; } } ////// Seal this freezable /// void ISealable.Seal() { Freeze(); } #endregion ISealable #region Internal Methods ////// Clears off the context storage and all Changed event handlers /// internal void ClearContextAndHandlers() { Freezable_UsingHandlerList = false; Freezable_UsingContextList = false; Freezable_UsingSingletonHandler = false; Freezable_UsingSingletonContext = false; _contextStorage = null; _property = null; } ////// Raises changed notifications for this Freezable. This includes /// calling the OnChanged virtual, invalidating sub properties, and /// raising the Changed event. /// internal void FireChanged() { // to avoid access costs, we start with calledHandlers at null and then // set it the first time we encounter change handlers that need to be stored. EventStorage calledHandlers = null; GetChangeHandlersAndInvalidateSubProperties(ref calledHandlers); // Fire all of the change handlers if (calledHandlers != null) { for (int i = 0, count = calledHandlers.Count; i < count; i++) { // Note: there is a known issue here where if one of these handlers // throws an exception, then we effectively will no longer be able to // use the EventStorage cache since it will not be possible to set its InUse flag // to false, and we will also keep any memory it was pointing to alive. // Everything will continue to function normally, however, we will be allocating // a new EventStorage each time rather than using the one stored in the cache. // Catching the exception and clearing the flag (and nulling // out the contents) will solve it, but due to Task #45099 on the exception // strategy for the property engine, this has not yet been implemented. // // call the function and then set to null to avoid hanging on to any // references. calledHandlers[i](this, EventArgs.Empty); calledHandlers[i] = null; } // we no longer need the EventStorage object - clear its contents and set // it to not be in use. calledHandlers.Clear(); calledHandlers.InUse = false; } } ////// Calling DependencyObject.Seal() on a Freezable will leave it in a weird /// state - it won't be free-threaded, but since Seal and Freeze use the /// same bit, the Freezable will think it is Frozen. We therefore disallow /// calling Seal() on a Freezable. /// internal override void Seal() { Invariant.Assert(false); } #endregion // Internal Methods #region Private methods internal bool Freeze(bool isChecking) { if (isChecking) { ReadPreamble(); return FreezeCore(true); } else if (!IsFrozenInternal) { WritePreamble(); // Check with derived classes to see how they feel about this. // If our caller didn't check CanFreeze this may throw // an exception. FreezeCore(false); // Any cached default values created using the FreezableDefaultValueFactory // must be removed and frozen. Leaving them alone is not an option since they will // attempt to promote themselves to locally-set if the user modifies them - // at that point this object will be sealed and the SetValue call will throw an // exception. For Freezables we're required to freeze all DPs, so for performance // we simply toss out the cache and return the frozen default prototype, which has // exactly the same state as the cached default (see PropertyMetadata.GetDefaultValue()). PropertyMetadata.RemoveAllCachedDefaultValues(this); // Since this object no longer changes it won't be able to notify dependents DependentListMapField.ClearValue(this); // The heart of Freeze. IsFrozen will now return // true, we keep the handler status bits since we haven't changed our // handler storage yet. Freezable_Frozen = true; this.DetachFromDispatcher(); // We do notify now, since we're "changing" to frozen. But not // until after everything below us is frozen. FireChanged(); // Clear off event handler/context flags when becoming frozen. We don't need to call // OnInheritanceContextChanged because a Frozen freezable has no one listening to its // InheritanceContextChanged event. Listeners are added when either a BindingExpression // or ResourceReferenceExpression is set into a DP. Both derive from Expression, and // calling Freeze on any Freezable with a DP set to an Expression will throw an exception. Debug_AssertNoInheritanceContextListeners(); ClearContextAndHandlers(); WritePostscript(); } return true; } // Makes a deep clone of a Freezable. Helper method for // CloneCore(), CloneCurrentValueCore() and GetAsFrozenCore() // // If useCurrentValue is true it calls GetValue on each of the sourceFreezable's DPs; if false // it uses ReadLocalValue. private void CloneCoreCommon(Freezable sourceFreezable, bool useCurrentValue, bool cloneFrozenValues) { EffectiveValueEntry[] srcEffectiveValues = sourceFreezable.EffectiveValues; uint srcEffectiveValueCount = sourceFreezable.EffectiveValuesCount; // Iterate through the effective values array. Note that default values aren't // stored here so the only defaults we'll come across are modified defaults, // which useCurrentValue = true uses and useCurrentValue = false ignores. for (uint i = 0; i < srcEffectiveValueCount; i++) { EffectiveValueEntry srcEntry = srcEffectiveValues[i]; DependencyProperty dp = DependencyProperty.RegisteredPropertyList.List[srcEntry.PropertyIndex]; // We need to skip ReadOnly properties otherwise SetValue will fail if ((dp != null) && !dp.ReadOnly) { object sourceValue; EntryIndex entryIndex = new EntryIndex(i); if (useCurrentValue) { // Default values aren't in the EffectiveValues array // so we won't see them as we iterate. We do copy modified defaults. Debug.Assert(srcEntry.BaseValueSourceInternal != BaseValueSourceInternal.Default || srcEntry.HasModifiers); sourceValue = sourceFreezable.GetValueEntry( entryIndex, dp, null, RequestFlags.FullyResolved).Value; // GetValue should not have returned UnsetValue Debug.Assert(sourceValue != DependencyProperty.UnsetValue); } else // use base values { // If the local value has modifiers, ReadLocalValue will return the base // value, which is what we want. A modified default will return UnsetValue, // which will be ignored at the call to SetValue sourceValue = sourceFreezable.ReadLocalValueEntry(entryIndex, dp, true /* allowDeferredReferences */); // For the useCurrentValue = false case we ignore any UnsetValues. if (sourceValue == DependencyProperty.UnsetValue) { continue; } // If the DP is an expression ReadLocalValue will return the actual expression. // In this case we need to copy it. if (srcEntry.IsExpression) { sourceValue = ((Expression)sourceValue).Copy(this, dp); } } // // If the value of the current DP is a Freezable // we need to recurse and call the appropriate Clone method in // order to do a deep copy. // Debug.Assert(!(sourceValue is Expression && sourceValue is Freezable), "This logic assumes Expressions and Freezables don't co-derive"); Freezable valueAsFreezable = sourceValue as Freezable; if (valueAsFreezable != null) { Freezable valueAsFreezableClone; // // Choose between the four possible ways of // cloning a Freezable // if (cloneFrozenValues) //CloneCore and CloneCurrentValueCore { valueAsFreezableClone = valueAsFreezable.CreateInstanceCore(); if (useCurrentValue) { // CloneCurrentValueCore implementation. We clone even if the // Freezable is frozen by recursing into CloneCurrentValueCore. valueAsFreezableClone.CloneCurrentValueCore(valueAsFreezable); } else { // CloneCore implementation. We clone even if the Freezable is // frozen by recursing into CloneCore. valueAsFreezableClone.CloneCore(valueAsFreezable); } sourceValue = valueAsFreezableClone; Debug_VerifyCloneCommon(valueAsFreezable, valueAsFreezableClone, /*isDeepClone=*/ true); } else // skip cloning frozen values { if (!valueAsFreezable.IsFrozen) { valueAsFreezableClone = valueAsFreezable.CreateInstanceCore(); if (useCurrentValue) { // GetCurrentValueAsFrozenCore implementation. Only clone if the // Freezable is mutable by recursing into GetCurrentValueAsFrozenCore. valueAsFreezableClone.GetCurrentValueAsFrozenCore(valueAsFreezable); } else { // GetAsFrozenCore implementation. Only clone if the Freezable is // mutable by recursing into GetAsFrozenCore. valueAsFreezableClone.GetAsFrozenCore(valueAsFreezable); } sourceValue = valueAsFreezableClone; Debug_VerifyCloneCommon(valueAsFreezable, valueAsFreezableClone, /*isDeepClone=*/ false); } } } SetValue(dp, sourceValue); } } } // Throws if owner/child are not context free and on different dispatchers. private static void EnsureConsistentDispatchers(DependencyObject owner, DependencyObject child) { Debug.Assert(owner != null && child != null, "Caller should guard against passing null owner/child."); // It is illegal to set a DependencyObject from one Dispatcher into a owner // being serviced by a different Dispatcher (i.e., they need to be on // the same thread or be context free (Dispatcher == null)) if (owner.Dispatcher != null && child.Dispatcher != null && owner.Dispatcher != child.Dispatcher) { throw new InvalidOperationException( SR.Get(SRID.Freezable_AttemptToUseInnerValueWithDifferentThread)); } } // These methods provide an abstraction for managing Freezable context // information - the context information being DO/DP pairs that the Freezable maps to. // // The methods will attempt to use as little memory as possible to store this information. // When there is only one context it will store the information directly, otherwise it will // place it within a list. When using a list, these methods place the DO/DP pairs so // that DOs are grouped together. This is done so that when walking the graph, it // is easier to track which DO's change handlers have already been gathered. // ////// Removes the context information for a Freezable. /// The DependencyObject to remove that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. /// private void RemoveContextInformation(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); bool failed = true; if (Freezable_UsingSingletonContext) { if (SingletonContext == context && SingletonContextProperty == property) { RemoveSingletonContext(); failed = false; } } else if (Freezable_UsingContextList) { FrugalObjectListlist = ContextList; int deadRefs = 0; int index = -1; int count = list.Count; for (int i = 0; i < count; i++) { FreezableContextPair entry = list[i]; object owner = entry.Owner.Target; if (owner != null) { if (failed && entry.Property == property && owner == context) { index = i; failed = false; } } else { ++deadRefs; } } if (index != -1) { Debug.Assert(!failed); list.RemoveAt(index); } PruneContexts(list, deadRefs); } // Make sure we actually removed something - if not throw an exception if (failed) { throw new ArgumentException(SR.Get(SRID.Freezable_NotAContext), "context"); } } /// /// Removes the single piece of contextual information that we have and updates all flags /// accordingly. /// private void RemoveSingletonContext() { Debug.Assert(Freezable_UsingSingletonContext); Debug.Assert(SingletonContext != null); if (HasHandlers) { _contextStorage = ((HandlerContextStorage)_contextStorage)._handlerStorage; } else { _contextStorage = null; } Freezable_UsingSingletonContext = false; } ////// Removes the context list and updates all flags accordingly. /// private void RemoveContextList() { Debug.Assert(Freezable_UsingContextList); if (HasHandlers) { _contextStorage = ((HandlerContextStorage)_contextStorage)._handlerStorage; } else { _contextStorage = null; } Freezable_UsingContextList = false; } ////// Helper function to add context information to a Freezable. /// /// The DependencyObject to add that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. internal override void AddInheritanceContext(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); // NTRAID#Longhorn-1677305-2006/05/25-[....] - Fix TemplateApplicationHelper::SetDependencyValueCore // // Debug_VerifyContextIsValid(context, property); if (!IsFrozenInternal) { DependencyObject oldInheritanceContext = InheritanceContext; AddContextInformation(context, property); // Check if the context has changed // If we are frozen, or we already had multiple contexts, the context has not changed if (oldInheritanceContext != InheritanceContext) { OnInheritanceContextChanged(EventArgs.Empty); } } } ////// Helper function to remove context information from a Freezable. /// /// The DependencyObject that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. internal override void RemoveInheritanceContext(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); if (!IsFrozenInternal) { DependencyObject oldInheritanceContext = InheritanceContext; RemoveContextInformation(context, property); // Check if the context has changed // If we are frozen, or we already had multiple contexts, the context has not changed if (oldInheritanceContext != InheritanceContext) { OnInheritanceContextChanged(EventArgs.Empty); } } } ////// Adds context information to a Freezable. /// The DependencyObject to add that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. /// internal void AddContextInformation(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); if (Freezable_UsingSingletonContext) { ConvertToContextList(); } if (Freezable_UsingContextList) { AddContextToList(context, property); } else { AddSingletonContext(context, property); } } ////// Helper function to convert to using a list to store context information. /// The SingletonContext is inserted into the list. /// private void ConvertToContextList() { Debug.Assert(Freezable_UsingSingletonContext); // The list is initialized with capacity for 2 entries since we // know we have a 2nd context to insert, hence the conversion // from the singleton context state. FrugalObjectListlist = new FrugalObjectList (2); // Note: This converts the SingletonContext from a strong reference to a WeakReference list.Add(new FreezableContextPair(SingletonContext, SingletonContextProperty)); if (HasHandlers) { ((HandlerContextStorage)_contextStorage)._contextStorage = list; } else { _contextStorage = list; } Freezable_UsingContextList = true; Freezable_UsingSingletonContext = false; // clear the singleton context property _property = null; } /// /// Helper function to add a singleton context to the Freezable's storage /// The DependencyObject to add that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. /// private void AddSingletonContext(DependencyObject context, DependencyProperty property) { Debug.Assert(!Freezable_UsingSingletonContext && !Freezable_UsingContextList); Debug.Assert(context != null); if (HasHandlers) { HandlerContextStorage hps = new HandlerContextStorage(); hps._handlerStorage = _contextStorage; hps._contextStorage = context; _contextStorage = hps; } else { _contextStorage = context; } // set the singleton context property _property = property; Freezable_UsingSingletonContext = true; } ////// Adds the context information to the context list. It does this by inserting the /// new context information in a location so that all context information referring /// to the same DO are grouped together. /// /// The DependencyObject to add that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. private void AddContextToList(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); FrugalObjectListlist = ContextList; int count = list.Count; int insertIndx = count; // insert at the end by default int deadRefs = 0; DependencyObject lastContext = null; bool multipleInheritanceContextsFound = HasMultipleInheritanceContexts; // We can never leave this state once there // bool newInheritanceContext = context.CanBeInheritanceContext && !this.IsInheritanceContextSealed; // becomes false if we find context on the list for (int i = 0; i < count; i++) { DependencyObject currentContext = (DependencyObject)list[i].Owner.Target; if (currentContext != null) { if (currentContext == context) { // insert after the last matching context insertIndx = i + 1; newInheritanceContext = false; } if (newInheritanceContext && !multipleInheritanceContextsFound) { if (currentContext != lastContext && currentContext.CanBeInheritanceContext) // Count remaining inheritance contexts { // We already found a previous inheritance context, so we have multiple ones multipleInheritanceContextsFound = true; Freezable_HasMultipleInheritanceContexts = true; } lastContext = currentContext; } } else { ++deadRefs; } } list.Insert(insertIndx, new FreezableContextPair(context, property)); PruneContexts(list, deadRefs); } private void PruneContexts(FrugalObjectList oldList, int numDead) { int count = oldList.Count; if (count - numDead == 0) { RemoveContextList(); } else if (numDead > 0) { FrugalObjectList newList = new FrugalObjectList (count - numDead); for (int i = 0; i < count; i++) { if (oldList[i].Owner.IsAlive) { newList.Add(oldList[i]); } } ContextList = newList; } } /// /// Helper function to get all of the event handlers for the Freezable and /// place them in the calledHandlers list. /// Where to place the change handlers for the Freezable. /// private void GetHandlers(ref EventStorage calledHandlers) { if (Freezable_UsingSingletonHandler) { if (calledHandlers == null) { calledHandlers = GetEventStorage(); } calledHandlers.Add(SingletonHandler); } else if (Freezable_UsingHandlerList) { if (calledHandlers == null) { calledHandlers = GetEventStorage(); } FrugalObjectListhandlers = HandlerList; for (int i = 0, count = handlers.Count; i < count; i++) { calledHandlers.Add(handlers[i]); } } } /// /// Add the specified EventHandler /// /// Handler to add private void HandlerAdd(EventHandler handler) { Debug.Assert(handler != null); if (Freezable_UsingSingletonHandler) { ConvertToHandlerList(); } if (Freezable_UsingHandlerList) { HandlerList.Add(handler); } else { AddSingletonHandler(handler); } } ////// Remove the specified EventHandler /// /// Handler to remove private void HandlerRemove(EventHandler handler) { bool failed = true; Debug.Assert(handler != null); if (Freezable_UsingSingletonHandler) { if (SingletonHandler == handler) { RemoveSingletonHandler(); failed = false; } } else if (Freezable_UsingHandlerList) { FrugalObjectListhandlers = HandlerList; int index = handlers.IndexOf(handler); if (index >= 0) { handlers.RemoveAt(index); failed = false; } if (handlers.Count == 0) { RemoveHandlerList(); } } if (failed) { throw new ArgumentException(SR.Get(SRID.Freezable_UnregisteredHandler), "handler"); } } // // Removes the singleton handler the Freezable is storing and resets // any state indicating this. // private void RemoveSingletonHandler() { Debug.Assert(Freezable_UsingSingletonHandler); if (HasContextInformation) { _contextStorage = ((HandlerContextStorage)_contextStorage)._contextStorage; } else { _contextStorage = null; } Freezable_UsingSingletonHandler = false; } // // Removes the handler list the Freezable is storing and resets // any state indicating this. // private void RemoveHandlerList() { Debug.Assert(Freezable_UsingHandlerList && HandlerList.Count == 0); if (HasContextInformation) { _contextStorage = ((HandlerContextStorage)_contextStorage)._contextStorage; } else { _contextStorage = null; } Freezable_UsingHandlerList = false; } /// /// Helper function to convert to using a list to store context information. /// The SingletonContext is inserted into the list. /// private void ConvertToHandlerList() { Debug.Assert(Freezable_UsingSingletonHandler); EventHandler singletonHandler = SingletonHandler; // The list is initialized with capacity for 2 entries since we // know we have a 2nd handler to insert, hence the conversion // from the singleton handler state. FrugalObjectListlist = new FrugalObjectList (2); list.Add(singletonHandler); if (HasContextInformation) { ((HandlerContextStorage)_contextStorage)._handlerStorage = list; } else { _contextStorage = list; } Freezable_UsingHandlerList = true; Freezable_UsingSingletonHandler = false; } // // helper function to add a singleton handler. The passed in handler parameter // will be stored as the singleton handler. // private void AddSingletonHandler(EventHandler handler) { Debug.Assert(!Freezable_UsingHandlerList && !Freezable_UsingSingletonHandler); Debug.Assert(handler != null); if (HasContextInformation) { HandlerContextStorage hps = new HandlerContextStorage(); hps._contextStorage = _contextStorage; hps._handlerStorage = handler; _contextStorage = hps; } else { _contextStorage = handler; } Freezable_UsingSingletonHandler = true; } #endregion #region Private properties //----------------------------------------------------- // // Private properties // //------------------------------------------------------ // // The below properties help at getting the singleton/list for the context or change handlers. // In all cases, if the other object exists (i.e. we want context, and there are also stored handlers), // then _contextStorage is HandlerContextStorage, so we need to get the data we want from that class. // /// /// Returns the context list the Freezable has. This function assumes /// that UsingContextList is true before being called. /// private FrugalObjectListContextList { get { Debug.Assert(Freezable_UsingContextList && !Freezable_UsingSingletonContext, "Must call UsingContextList before use"); if (HasHandlers) { HandlerContextStorage ptrStorage = (HandlerContextStorage)_contextStorage; return (FrugalObjectList )ptrStorage._contextStorage; } else { return (FrugalObjectList )_contextStorage; } } set { Debug.Assert(Freezable_UsingContextList && !Freezable_UsingSingletonContext, "Must call UsingContextList before use"); if (HasHandlers) { ((HandlerContextStorage)_contextStorage)._contextStorage = value; } else { _contextStorage = value; } } } /// /// Returns the handler list the Freezable has. This function assumes /// the handlers for the Freezable are stored in a list. /// private FrugalObjectListHandlerList { get { Debug.Assert(Freezable_UsingHandlerList && !Freezable_UsingSingletonHandler, "Must call UsingHandlerList before use"); if (HasContextInformation) { HandlerContextStorage ptrStorage = (HandlerContextStorage)_contextStorage; return (FrugalObjectList )ptrStorage._handlerStorage; } else { return (FrugalObjectList )_contextStorage; } } } /// /// Returns the singleton handler the Freezable has. This function assumes /// that UsingSingletonHandler is true before being called. /// private EventHandler SingletonHandler { get { Debug.Assert(Freezable_UsingSingletonHandler && !Freezable_UsingHandlerList, "Must call UsingSingletonHandler before use"); if (HasContextInformation) { HandlerContextStorage ptrStorage = (HandlerContextStorage)_contextStorage; return (EventHandler)ptrStorage._handlerStorage; } else { return (EventHandler)_contextStorage; } } } ////// Returns the singleton context the Freezable has. This function assumes /// that UsingSingletonContext is true before being called. /// private DependencyObject SingletonContext { get { Debug.Assert(Freezable_UsingSingletonContext && !Freezable_UsingContextList, "Must call UsingSingletonContext before use"); if (HasHandlers) { HandlerContextStorage ptrStorage = (HandlerContextStorage)_contextStorage; return (DependencyObject)ptrStorage._contextStorage; } else { return (DependencyObject)_contextStorage; } } } ////// Returns/sets the singleton context property of the Freezable. This /// function assumes that UsingSingletonContext is true before being called. /// private DependencyProperty SingletonContextProperty { get { Debug.Assert(Freezable_UsingSingletonContext && !Freezable_UsingContextList, "Must call UsingSingletonContext before use"); return (DependencyProperty)_property; } } ////// Whether the Freezable has event handlers. /// private bool HasHandlers { get { return (Freezable_UsingHandlerList || Freezable_UsingSingletonHandler); } } ////// Whether the Freezable has context information. /// private bool HasContextInformation { get { return (Freezable_UsingContextList || Freezable_UsingSingletonContext); } } #endregion #region InheritanceContext ////// InheritanceContext /// internal override DependencyObject InheritanceContext { [FriendAccessAllowed] // Built into Base, also used by Core and Framework. get { if (!Freezable_HasMultipleInheritanceContexts) { if (Freezable_UsingSingletonContext) // We have exactly one Freezable context { DependencyObject singletonContext = SingletonContext; if (singletonContext.CanBeInheritanceContext) { return singletonContext; } } else if (Freezable_UsingContextList) { // We have multiple Freezable contexts, but at most one context is valid FrugalObjectListlist = ContextList; int count = list.Count; for (int i = 0; i < count; i++) { DependencyObject currentContext = (DependencyObject)list[i].Owner.Target; if (currentContext != null && currentContext.CanBeInheritanceContext) { // This is the first and only valid inheritance context we should find return currentContext; } } } } return null; // If we have gotten here, we have either multiple or no valid contexts } } /// /// HasMultipleInheritanceContexts /// internal override bool HasMultipleInheritanceContexts { [FriendAccessAllowed] // Built into Base, also used by Core and Framework. get { return Freezable_HasMultipleInheritanceContexts; } } #endregion InheritanceContext // // A simple class that is used when the Freezable needs to store both handlers and context info. // The _handlerStorage and _contextStorage fields can store either a list or a direct // reference to the object - Freezable's Freezable_* flags (actually added to DependencyObject.cs) // can be used to test for which one to use. // private class HandlerContextStorage { public object _handlerStorage; public object _contextStorage; } // // A simple struct that stores a weak ref to a dependency object and a corresponding property // of that object. // private struct FreezableContextPair { public FreezableContextPair(DependencyObject dependObject, DependencyProperty dependProperty) { Owner = new WeakReference(dependObject); Property = dependProperty; } public readonly WeakReference Owner; public readonly DependencyProperty Property; } // // A simple class that is used to cache the event handlers that are gathered during a call // to FireChanged. Using this cache cuts down on the amount of managed allocations, which // improves the performance of Freezables. // private class EventStorage { public EventStorage(int initialSize) { // check just in case if (initialSize <= 0) initialSize = 1; _events = new EventHandler[initialSize]; _logSize = 0; _physSize = initialSize; _inUse = false; } // // Adds a new EventHandler to the storage. In the case that more memory is needed, the cache // size is doubled. // public void Add(EventHandler e) { if (_logSize == _physSize) { _physSize *= 2; EventHandler[] temp = new EventHandler[_physSize]; for (int i = 0; i < _logSize; i++) { temp[i] = _events[i]; } _events = temp; } _events[_logSize] = e; _logSize++; } // // Clears the list but does not free the memory so that future uses of the // class can reuse the space and not take an allocation performance hit. // public void Clear() { _logSize = 0; } public int Count { get { return _logSize; } } public int PhysicalSize { get { return _physSize; } } public EventHandler this[int idx] { get { return _events[idx]; } set { _events[idx] = value; } } // // So that it's possible to reuse EventStorage classes, and so that if one is being used, another // person does not overwrite the contents (i.e. FireChanged causes someone else call their FireChanged), // an InUse flag is set to indicate whether someone is currently using this class. // public bool InUse { get { return _inUse; } set { _inUse = value; } } EventHandler[] _events; // list of events int _logSize; // the logical size of the list int _physSize; // the allocated buffer size bool _inUse; } //----------------------------------------------------- // // Debug fields // //------------------------------------------------------ #region Debug // Verify a clone. If isDeepClone is true we make sure that the cloned object is not the same as the // original. GetAsFrozen and GetCurrentValueAsFrozen do not do deep clones since they will immediately // return any frozen originals rather than cloning them. private static void Debug_VerifyCloneCommon(Freezable original, object clone, bool isDeepClone) { if (Invariant.Strict) { Freezable cloneAsFreezable = (Freezable) clone; Debug_VerifyInstance("CloneCore", original, cloneAsFreezable); // Extra CloneCommon checks if (isDeepClone) { Invariant.Assert(clone != original, "CloneCore should not return the same instance as the original."); } Invariant.Assert(!cloneAsFreezable.HasHandlers, "CloneCore should not have handlers attached on construction."); IList originalAsIList = original as IList; if (originalAsIList != null) { // we've already checked that original and clone are the same type IList cloneAsIList = clone as IList; Invariant.Assert(originalAsIList.Count == cloneAsIList.Count, "CloneCore didn't clone all of the elements in the list."); for (int i = 0; i < cloneAsIList.Count; i++) { Freezable originalItemAsFreezable = originalAsIList[i] as Freezable; Freezable cloneItemAsFreezable = cloneAsIList[i] as Freezable; if (isDeepClone && cloneItemAsFreezable != null && cloneItemAsFreezable != null) { Invariant.Assert(originalItemAsFreezable != cloneItemAsFreezable, "CloneCore didn't clone the elements in the list correctly."); } } } } } private static void Debug_VerifyInstance(String methodName, Freezable original, Freezable newInstance) { if (Invariant.Strict) { Invariant.Assert(newInstance != null, "{0} should not return null.", methodName); Invariant.Assert(newInstance.GetType() == original.GetType(), String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} should return instance of same type. (Expected= '{1}', Actual='{2}')", methodName, original.GetType(), newInstance.GetType())); Invariant.Assert(!newInstance.IsFrozen, "{0} should return a mutable instance. Recieved a frozen instance.", methodName); } } // Enumerates our FreezableContextPairs and (when we have full DP information) // verifies that the context is still valid. private void Debug_DetectContextLeaks() { if (Invariant.Strict) { if (Freezable_UsingSingletonContext) { Debug_VerifyContextIsValid(SingletonContext, SingletonContextProperty); } else if (Freezable_UsingContextList) { FrugalObjectListcontextList = ContextList; for(int i = 0, count = ContextList.Count; i < count; i++) { FreezableContextPair context = ContextList[i]; DependencyObject owner = (DependencyObject) context.Owner.Target; if (!context.Owner.IsAlive) { // If the WeakReference is no longer alive the owner which // was "using" this Freezable has been GC'ed. // // There is no way to verify that this object was pointing // to us pre-collection, but in theory it was and we are // just waiting for compaction. continue; } Debug_VerifyContextIsValid(owner, context.Property); } } } } // Verifies that the given owner/property pair constitutes a valid // inheritance context for this Freezable. This is a no-op if the // property is null. private void Debug_VerifyContextIsValid(DependencyObject owner, DependencyProperty property) { if (Invariant.Strict) { Invariant.Assert(owner != null, "We should not have null owners in the ContextList/SingletonContext."); if (property == null) { // This context was not made through a DependencyProperty. There is // nothing we can verify. return; } // If we have DP information for the context, we can verify that // the property on the owner is still referencing us. Example: // // (Pen.Brush) // // .-----. // ' v // Pen Brush // ^ . // '-----' // // Context // // If the owner's DP value does not point to us than we've leaked // a context. DependencyObject ownerAsDO = (DependencyObject) owner; object effectiveValue = ownerAsDO.GetValue(property); // There is a notable exception to the rule above, which is that // ResourceDictionaries create a context between the resource and // the FE which owns the resource. // // In this case, the connection will be made via the pragmatic, // but somewhat arbitrarily chosen VisualBrush.Visual DP. // // See comments in ResourceDictionary.AddInheritanceContext. Note // that the owner will be the FE which owns the ResourceDictionary, // not the ResourceDictionary itself. bool mayBeResourceDictionary = property.Name == "Visual" && property.OwnerType.FullName == "System.Windows.Media.VisualBrush" && owner.GetType().FullName != "System.Windows.Media.VisualBrush"; // ResourceDictionaries may not be owned by a VisualBrush. // NTRAID#Longhorn-1692587-2006/06/06-[....] - Find a way to bring back context verification. // // Invariant.Assert(effectiveValue == this || mayBeResourceDictionary, // String.Format(System.Globalization.CultureInfo.InvariantCulture, // "Detected context leak: Property '{0}.{1}' on {2}. Expected '{3}', Actual '{4}'", // property.OwnerType.Name, // property.Name, // owner.GetType().FullName, // this, // effectiveValue)); } } #endregion Debug //------------------------------------------------------ // // Private fields // //----------------------------------------------------- #region Private Fields // For the common case of having only a single context, we use _property // to store the DependencyProperty that goes with the single context. private DependencyProperty _property; // initial size to make the EventStorage cache private const int INITIAL_EVENTSTORAGE_SIZE = 4; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // // Copyright (C) Microsoft Corporation. All rights reserved. // // // // Description: The Freezable class (plus the FreezableHelper class) // encompasses all of the Freezable pattern. // // See spec at http://avalon/medialayer/Shared%20Documents/Freezables.doc // // History: // 05/01/2003 : [....] - Created // 07/11/2003 : [....] - Removed subsequent history. See SourceDepot. // 08/05/2005 : t-kuberg - Added context information. // //--------------------------------------------------------------------------- using System; using System.Diagnostics; using System.Collections; using System.Collections.Specialized; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using System.Windows.Threading; using MS.Internal; // for Invariant using MS.Internal.WindowsBase; // FriendAccessAllowed using MS.Utility; // FrugalList namespace System.Windows { ////// The Freezable class encapsulates the Freezable pattern for DOs whose /// values can potentially be frozen. See the Freezable documentation for /// more details. /// public abstract class Freezable : DependencyObject, ISealable { #if DEBUG private static int _nextID = 1; private readonly int DebugID = _nextID++; #endif #region Protected Constructors //----------------------------------------------------- // // Protected constructors // //----------------------------------------------------- ////// Construct a mutable Freezable. /// protected Freezable() { Debug.Assert(!Freezable_Frozen && !Freezable_HasMultipleInheritanceContexts && !(HasHandlers || HasContextInformation), "Initial state is incorrect"); } #endregion #region Public Methods //------------------------------------------------------ // // Public methods // //----------------------------------------------------- ////// Makes a mutable deep base value clone of this Freezable. /// /// Caveat: Frozen default values will still be frozen afterwards /// ///A clone of the Freezable. public Freezable Clone() { ReadPreamble(); Freezable clone = CreateInstance(); clone.CloneCore(this); Debug_VerifyCloneCommon(/* original = */ this, /* clone = */ clone, /* isDeepClone = */ true); return clone; } ////// Makes a mutable current value clone of this Freezable. /// /// Caveat: Frozen default values will still be frozen afterwards /// ////// Returns a mutable deep copy of this Freezable that represents /// its current state. /// public Freezable CloneCurrentValue() { ReadPreamble(); Freezable clone = CreateInstance(); clone.CloneCurrentValueCore(this); // Freezable implementers who override CloneCurrentValueCore must ensure that // on creation the copy is not frozen. Debug_VerifyCloneCommon checks for this, // among other things. Debug_VerifyCloneCommon(/* original = */ this, /* clone = */ clone, /* isDeepClone = */ true); return clone; } ////// Semantically equivalent to Freezable.Clone().Freeze() except that /// GetAsFrozen avoids a copying any portions of the Freezable graph /// which are already frozen. /// public Freezable GetAsFrozen() { ReadPreamble(); if (IsFrozenInternal) { return this; } Freezable clone = CreateInstance(); clone.GetAsFrozenCore(this); Debug_VerifyCloneCommon(/* original = */ this, /* clone = */ clone, /* isDeepClone = */ false); clone.Freeze(); return clone; } ////// Semantically equivalent to Freezable.CloneCurrentValue().Freeze() except that /// GetCurrentValueAsFrozen avoids a copying any portions of the Freezable graph /// which are already frozen. /// public Freezable GetCurrentValueAsFrozen() { ReadPreamble(); if (IsFrozenInternal) { return this; } Freezable clone = CreateInstance(); clone.GetCurrentValueAsFrozenCore(this); Debug_VerifyCloneCommon(/* original = */ this, /* clone = */ clone, /* isDeepClone = */ false); clone.Freeze(); return clone; } ////// True if this Freezable can be frozen (by calling Freeze()) /// public bool CanFreeze { get { return IsFrozenInternal || FreezeCore(/* isChecking = */ true); } } ////// Does an in-place modification to make the object frozen. It is legal to /// call this on values that are already frozen. /// ///This exception /// will be thrown if this Freezable can't be frozen. Use /// the CanFreeze property to detect this in advance. public void Freeze() { // Check up front that the operation will succeed before we begin. if (!CanFreeze) { throw new InvalidOperationException(SR.Get(SRID.Freezable_CantFreeze)); } Freeze(/* isChecking = */ false); } #endregion #region Public Properties //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ ////// Returns whether or not the Freezable is modifiable. Attempts /// to set properties on an IsFrozen value result /// in exceptions being raised. /// public bool IsFrozen { get { ReadPreamble(); return IsFrozenInternal; } } internal bool IsFrozenInternal { get { return Freezable_Frozen; } } #endregion #region Public Events //----------------------------------------------------- // // Public Events // //------------------------------------------------------ ////// The Changed event is raised whenever something on this /// Freezable is modified. Note that it is illegal to /// add or remove event handlers from a value with /// IsFrozen. /// ////// An attempt was made to modify the Changed handler of /// a value with IsFrozen == true. /// public event EventHandler Changed { add { WritePreamble(); if (value != null) { ChangedInternal += value; } } remove { WritePreamble(); if (value != null) { ChangedInternal -= value; } } } internal event EventHandler ChangedInternal { add { HandlerAdd(value); // Adding/Removing Changed handlers does not raise the Changed event. // Therefore we intentionally do not call WritePostscript(). } remove { HandlerRemove(value); // Adding/Removing Changed handlers does not raise the Changed event. // Therefore we intentionally do not call WritePostscript(). } } #endregion #region Protected Methods //----------------------------------------------------- // // Protected methods // //----------------------------------------------------- ////// Override OnPropertyChanged so that we can fire the Freezable's Changed /// handler in response to a DP changing. /// protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); // The property system will call us back when a SetValue is performed // on a Freezable. The Freezable then walks it's contexts and causes // a subproperty invalidation on each context and fires any changed // handlers that have been registered. // When a default value is being promoted to a local value the sub property // change that caused the promotion is being merged with the value promotion // change. This fix was implemented for DevDivBug#108642. It is required to // detect this case specially and propagate subproperty invalidations for it. if (!e.IsASubPropertyChange || e.OperationType == OperationType.ChangeMutableDefaultValue) { WritePostscript(); } // OnPropertyChanged is called after the old inheritance context is // removed, but before the new one is added. Debug_DetectContextLeaks(); } ////// Create a default instance of a Freezable object. Actual allocation /// will occur in CreateInstanceCore. /// ///A new instance of the class protected Freezable CreateInstance() { Freezable newFreezable = CreateInstanceCore(); Debug_VerifyInstance("CreateInstance", this, newFreezable); return newFreezable; } // ////// Subclasses must implement this to create instances of themselves. /// See the Freezable documentation for examples. /// ///A new instance of the class protected abstract Freezable CreateInstanceCore(); ////// If you derive from Freezable you may need to override this method. Reasons /// to override include: /// 1) Your subclass has data that is not exposed via DPs /// 2) Your subclass has to perform extra work during construction. For /// example, your subclass implements ISupportInitialize. /// /// The default implementation makes deep clones of all writable, locally set /// properties including expressions. The property's base value is copied -- not the /// current value. It skips read only DPs. /// /// If you do override this method, you MUST call the base implementation. /// /// This is called by Clone(). /// /// The Freezable to clone information from protected virtual void CloneCore(Freezable sourceFreezable) { CloneCoreCommon(sourceFreezable, /* useCurrentValue = */ false, /* cloneFrozenValues = */ true); } ////// If you derive from Freezable you may need to override this method. Reasons /// to override include: /// 1) Your subclass has data that is not exposed via DPs /// 2) Your subclass has to perform extra work during construction. For /// example, your subclass implements ISupportInitialize. /// /// The default implementation goes through all DPs making copies of their /// current values. It skips read only and default DPs /// /// If you do override this method, you MUST call the base implementation. /// /// This is called by CloneCurrentValue(). /// /// The Freezable to copy info from protected virtual void CloneCurrentValueCore(Freezable sourceFreezable) { CloneCoreCommon(sourceFreezable, /* useCurrentValue = */ true, /* cloneFrozenValues = */ true); } ////// If you derive from Freezable you may need to override this method. Reasons /// to override include: /// 1) Your subclass has data that is not exposed via DPs /// 2) Your subclass has to perform extra work during construction. For /// example, your subclass implements ISupportInitialize. /// /// The default implementation makes clones of all writable, unfrozen, locally set /// properties including expressions. The property's base value is copied -- not the /// current value. It skips read only DPs and any values which are already frozen. /// /// If you do override this method, you MUST call the base implementation. /// /// You do not need to Freeze values as they are copied. The result will be /// frozen by GetAsFrozen() before being returned. /// /// This is called by GetAsFrozen(). /// /// The Freezable to clone information from protected virtual void GetAsFrozenCore(Freezable sourceFreezable) { CloneCoreCommon(sourceFreezable, /* useCurrentValue = */ false, /* cloneFrozenValues = */ false); } ////// If you derive from Freezable you may need to override this method. Reasons /// to override include: /// 1) Your subclass has data that is not exposed via DPs /// 2) Your subclass has to perform extra work during construction. For /// example, your subclass implements ISupportInitialize. /// /// The default implementation goes through all DPs making copies of their /// current values. It skips read only DPs and any values which are already frozen. /// /// If you do override this method, you MUST call the base implementation. /// /// You do not need to Freeze values as they are copied. The result will be /// frozen by GetCurrentValueAsFrozen() before being returned. /// /// This is called by GetCurrentValueAsFrozen(). /// /// The Freezable to clone information from protected virtual void GetCurrentValueAsFrozenCore(Freezable sourceFreezable) { CloneCoreCommon(sourceFreezable, /* useCurrentValue = */ true, /* cloneFrozenValues = */ false); } ////// If you derive from Freezable you will need to override this if your subclass /// has data that is not exposed via DPs. /// /// The default implementation goes through all DPs and returns false /// if any DP has an expression or if any Freezable DP cannot freeze. /// /// If you do override this method, you MUST call the base implementation. /// /// This is called by Freeze(). /// /// If this is true, the method will just check /// to see that the object can be frozen, but won't actually freeze it. /// ///True if the Freezable is or can be frozen. protected virtual bool FreezeCore(bool isChecking) { EffectiveValueEntry[] effectiveValues = EffectiveValues; uint numEffectiveValues = EffectiveValuesCount; // Loop through all DPs and call their FreezeValueCallback. for (uint i = 0; i < numEffectiveValues; i++) { DependencyProperty dp = DependencyProperty.RegisteredPropertyList.List[effectiveValues[i].PropertyIndex]; if (dp != null) { EntryIndex entryIndex = new EntryIndex(i); PropertyMetadata metadata = dp.GetMetadata(DependencyObjectType); FreezeValueCallback freezeValueCallback = metadata.FreezeValueCallback; if(!freezeValueCallback(this, dp, entryIndex, metadata, isChecking)) { return false; } } } return true; } // // _eventStorage is used as a performance/memory speedup when firing change handlers. // It exists once per thread for thread safety, and is used to store the list of change // handlers that are gathered by GetChangeHandlersAndInvalidateSubProperties. Reusing the // same EventStorage gives gains because it doesn't need to be reallocated each time // FireChanged occurs. // [ThreadStatic] static private EventStorage _eventStorage = null; ////// Property to access and intialize the thread static _eventStorage variable. /// private EventStorage CachedEventStorage { get { // make sure _eventStorage is not null - with ThreadStatic it appears that the second // thread to access the variable will set this to null if (_eventStorage == null) { _eventStorage = new EventStorage(INITIAL_EVENTSTORAGE_SIZE); } return _eventStorage; } } ////// Gets an EventStorage object to be used to cache event handlers and sets it to be /// in use. /// ////// An EventStorage object to be used to cache event handlers that is set /// to be in use. /// private EventStorage GetEventStorage() { EventStorage eventStorage = CachedEventStorage; // if we reach a case where EventStorage is being used - meaning FireChanged called // a handler that in turn called FireChanged which is probably a bad thing to have // happen - just allocate a new one that won't be cached. if (eventStorage.InUse) { // use the cached EventStorage's physical size as an estimate of how big we // need to be in order to avoid growing the newly created EventStorage int cachedPhysicalSize = eventStorage.PhysicalSize; eventStorage = new EventStorage(cachedPhysicalSize); } eventStorage.InUse = true; return eventStorage; } ////// This method is called when a modification happens to the Freezable object. /// protected virtual void OnChanged() { } ////// This method walks up the context graph recursively, gathering all change handlers that /// exist at or above the current node, placing them in calledHandlers. While /// performing the walk it will also call OnChanged and InvalidateSubProperty on all /// DO/DP pairs encountered on the walk. /// private void GetChangeHandlersAndInvalidateSubProperties(ref EventStorage calledHandlers) { this.OnChanged(); Freezable contextAsFreezable; if (Freezable_UsingSingletonContext) { DependencyObject context = SingletonContext; contextAsFreezable = context as Freezable; if (contextAsFreezable != null) { contextAsFreezable.GetChangeHandlersAndInvalidateSubProperties(ref calledHandlers); } if (SingletonContextProperty != null) { context.InvalidateSubProperty(SingletonContextProperty); } } else if (Freezable_UsingContextList) { FrugalObjectListcontextList = ContextList; DependencyObject lastDO = null; int deadRefs = 0; for (int i = 0, count = contextList.Count; i < count; i++) { FreezableContextPair currentContext = contextList[i]; DependencyObject currentDO = (DependencyObject)currentContext.Owner.Target; if (currentDO != null) { // we only want to grab change handlers once per context reference - so skip // until we find a new one if (currentDO != lastDO) { contextAsFreezable = currentDO as Freezable; if (contextAsFreezable != null) { contextAsFreezable.GetChangeHandlersAndInvalidateSubProperties(ref calledHandlers); } lastDO = currentDO; } if (currentContext.Property != null) { currentDO.InvalidateSubProperty(currentContext.Property); } } else { ++deadRefs; } } PruneContexts(contextList, deadRefs); } GetHandlers(ref calledHandlers); } /// /// Extenders of Freezable must call this method at the beginning of any /// public API which reads the state of the object. (e.g., a proprety getter.) /// This ensures that the object is being accessed from a valid thread. /// protected void ReadPreamble() { VerifyAccess(); } ////// Extenders of Freezable must call this method prior to changing the state /// of the object (e.g. the beginning of a property setter.) This ensures that /// the object is not frozen and is being accessed from a valid thread. /// protected void WritePreamble() { VerifyAccess(); if (IsFrozenInternal) { throw new InvalidOperationException( SR.Get(SRID.Freezable_CantBeFrozen,GetType().FullName)); } } ////// Extenders of Freezable must call this method at the end of an API which /// changed the state of the object (e.g., at the end of a property setter) to /// raise the Changed event. Multiple state changes within a method or /// property may be "batched" into a single call to WritePostscript(). /// protected void WritePostscript() { FireChanged(); } ////// Extenders of Freezable call this to set in a new value for internal /// properties or other embedded values that themselves are DependencyObjects. /// This method insures that the appropriate context pointers are set up for /// the old and the new Dependency objects. /// /// In this version the property is set to be null since /// it is not explicitly specified. /// /// /// The previous value of the property. /// The new value to set into the property protected void OnFreezablePropertyChanged( DependencyObject oldValue, DependencyObject newValue ) { OnFreezablePropertyChanged(oldValue, newValue, null); } ////// Extenders of Freezable call this to set in a new value for internal /// properties or other embedded values that themselves are DependencyObjects. /// This method insures that the appropriate context pointers are set up for /// the old and the new DependencyObject objects. /// /// The previous value of the property. /// The new value to set into the property /// The property that is being changed or null if none protected void OnFreezablePropertyChanged( DependencyObject oldValue, DependencyObject newValue, DependencyProperty property ) { // NTRAID#Longhorn-1023842 -4/27/2005-[....] // // We should ensure dispatchers are consistent *before* modifying // changed handlers, otherwise we will leave the freezable in an // inconsistent state. // if (newValue != null) { EnsureConsistentDispatchers(this, newValue); } if (oldValue != null) { RemoveSelfAsInheritanceContext(oldValue, property); } if (newValue != null) { ProvideSelfAsInheritanceContext(newValue, property); } } ////// Helper method that just invokes Freeze on provided /// Freezable if it's not null. Otherwise it doesn't do anything. /// /// Freezable to freeze. /// If this is true, the method will just check /// to see that the object can be frozen, but won't actually freeze it. /// ///True if the Freezable was or can be frozen. /// False if isChecking was true and the Freezable can't be frozen. /// ///This exception /// will be thrown if isChecking is passed in as false and this /// Freezable can't be frozen. // static protected internal bool Freeze(Freezable freezable, bool isChecking) { if (freezable != null) { return freezable.Freeze(isChecking); } //I guess something that's null is always frozen. return true; } #endregion // Protected Methods #region ISealable /// /// Can this freezable be sealed /// bool ISealable.CanSeal { get { return CanFreeze; } } ////// Is this freezable sealed /// bool ISealable.IsSealed { get { return IsFrozen; } } ////// Seal this freezable /// void ISealable.Seal() { Freeze(); } #endregion ISealable #region Internal Methods ////// Clears off the context storage and all Changed event handlers /// internal void ClearContextAndHandlers() { Freezable_UsingHandlerList = false; Freezable_UsingContextList = false; Freezable_UsingSingletonHandler = false; Freezable_UsingSingletonContext = false; _contextStorage = null; _property = null; } ////// Raises changed notifications for this Freezable. This includes /// calling the OnChanged virtual, invalidating sub properties, and /// raising the Changed event. /// internal void FireChanged() { // to avoid access costs, we start with calledHandlers at null and then // set it the first time we encounter change handlers that need to be stored. EventStorage calledHandlers = null; GetChangeHandlersAndInvalidateSubProperties(ref calledHandlers); // Fire all of the change handlers if (calledHandlers != null) { for (int i = 0, count = calledHandlers.Count; i < count; i++) { // Note: there is a known issue here where if one of these handlers // throws an exception, then we effectively will no longer be able to // use the EventStorage cache since it will not be possible to set its InUse flag // to false, and we will also keep any memory it was pointing to alive. // Everything will continue to function normally, however, we will be allocating // a new EventStorage each time rather than using the one stored in the cache. // Catching the exception and clearing the flag (and nulling // out the contents) will solve it, but due to Task #45099 on the exception // strategy for the property engine, this has not yet been implemented. // // call the function and then set to null to avoid hanging on to any // references. calledHandlers[i](this, EventArgs.Empty); calledHandlers[i] = null; } // we no longer need the EventStorage object - clear its contents and set // it to not be in use. calledHandlers.Clear(); calledHandlers.InUse = false; } } ////// Calling DependencyObject.Seal() on a Freezable will leave it in a weird /// state - it won't be free-threaded, but since Seal and Freeze use the /// same bit, the Freezable will think it is Frozen. We therefore disallow /// calling Seal() on a Freezable. /// internal override void Seal() { Invariant.Assert(false); } #endregion // Internal Methods #region Private methods internal bool Freeze(bool isChecking) { if (isChecking) { ReadPreamble(); return FreezeCore(true); } else if (!IsFrozenInternal) { WritePreamble(); // Check with derived classes to see how they feel about this. // If our caller didn't check CanFreeze this may throw // an exception. FreezeCore(false); // Any cached default values created using the FreezableDefaultValueFactory // must be removed and frozen. Leaving them alone is not an option since they will // attempt to promote themselves to locally-set if the user modifies them - // at that point this object will be sealed and the SetValue call will throw an // exception. For Freezables we're required to freeze all DPs, so for performance // we simply toss out the cache and return the frozen default prototype, which has // exactly the same state as the cached default (see PropertyMetadata.GetDefaultValue()). PropertyMetadata.RemoveAllCachedDefaultValues(this); // Since this object no longer changes it won't be able to notify dependents DependentListMapField.ClearValue(this); // The heart of Freeze. IsFrozen will now return // true, we keep the handler status bits since we haven't changed our // handler storage yet. Freezable_Frozen = true; this.DetachFromDispatcher(); // We do notify now, since we're "changing" to frozen. But not // until after everything below us is frozen. FireChanged(); // Clear off event handler/context flags when becoming frozen. We don't need to call // OnInheritanceContextChanged because a Frozen freezable has no one listening to its // InheritanceContextChanged event. Listeners are added when either a BindingExpression // or ResourceReferenceExpression is set into a DP. Both derive from Expression, and // calling Freeze on any Freezable with a DP set to an Expression will throw an exception. Debug_AssertNoInheritanceContextListeners(); ClearContextAndHandlers(); WritePostscript(); } return true; } // Makes a deep clone of a Freezable. Helper method for // CloneCore(), CloneCurrentValueCore() and GetAsFrozenCore() // // If useCurrentValue is true it calls GetValue on each of the sourceFreezable's DPs; if false // it uses ReadLocalValue. private void CloneCoreCommon(Freezable sourceFreezable, bool useCurrentValue, bool cloneFrozenValues) { EffectiveValueEntry[] srcEffectiveValues = sourceFreezable.EffectiveValues; uint srcEffectiveValueCount = sourceFreezable.EffectiveValuesCount; // Iterate through the effective values array. Note that default values aren't // stored here so the only defaults we'll come across are modified defaults, // which useCurrentValue = true uses and useCurrentValue = false ignores. for (uint i = 0; i < srcEffectiveValueCount; i++) { EffectiveValueEntry srcEntry = srcEffectiveValues[i]; DependencyProperty dp = DependencyProperty.RegisteredPropertyList.List[srcEntry.PropertyIndex]; // We need to skip ReadOnly properties otherwise SetValue will fail if ((dp != null) && !dp.ReadOnly) { object sourceValue; EntryIndex entryIndex = new EntryIndex(i); if (useCurrentValue) { // Default values aren't in the EffectiveValues array // so we won't see them as we iterate. We do copy modified defaults. Debug.Assert(srcEntry.BaseValueSourceInternal != BaseValueSourceInternal.Default || srcEntry.HasModifiers); sourceValue = sourceFreezable.GetValueEntry( entryIndex, dp, null, RequestFlags.FullyResolved).Value; // GetValue should not have returned UnsetValue Debug.Assert(sourceValue != DependencyProperty.UnsetValue); } else // use base values { // If the local value has modifiers, ReadLocalValue will return the base // value, which is what we want. A modified default will return UnsetValue, // which will be ignored at the call to SetValue sourceValue = sourceFreezable.ReadLocalValueEntry(entryIndex, dp, true /* allowDeferredReferences */); // For the useCurrentValue = false case we ignore any UnsetValues. if (sourceValue == DependencyProperty.UnsetValue) { continue; } // If the DP is an expression ReadLocalValue will return the actual expression. // In this case we need to copy it. if (srcEntry.IsExpression) { sourceValue = ((Expression)sourceValue).Copy(this, dp); } } // // If the value of the current DP is a Freezable // we need to recurse and call the appropriate Clone method in // order to do a deep copy. // Debug.Assert(!(sourceValue is Expression && sourceValue is Freezable), "This logic assumes Expressions and Freezables don't co-derive"); Freezable valueAsFreezable = sourceValue as Freezable; if (valueAsFreezable != null) { Freezable valueAsFreezableClone; // // Choose between the four possible ways of // cloning a Freezable // if (cloneFrozenValues) //CloneCore and CloneCurrentValueCore { valueAsFreezableClone = valueAsFreezable.CreateInstanceCore(); if (useCurrentValue) { // CloneCurrentValueCore implementation. We clone even if the // Freezable is frozen by recursing into CloneCurrentValueCore. valueAsFreezableClone.CloneCurrentValueCore(valueAsFreezable); } else { // CloneCore implementation. We clone even if the Freezable is // frozen by recursing into CloneCore. valueAsFreezableClone.CloneCore(valueAsFreezable); } sourceValue = valueAsFreezableClone; Debug_VerifyCloneCommon(valueAsFreezable, valueAsFreezableClone, /*isDeepClone=*/ true); } else // skip cloning frozen values { if (!valueAsFreezable.IsFrozen) { valueAsFreezableClone = valueAsFreezable.CreateInstanceCore(); if (useCurrentValue) { // GetCurrentValueAsFrozenCore implementation. Only clone if the // Freezable is mutable by recursing into GetCurrentValueAsFrozenCore. valueAsFreezableClone.GetCurrentValueAsFrozenCore(valueAsFreezable); } else { // GetAsFrozenCore implementation. Only clone if the Freezable is // mutable by recursing into GetAsFrozenCore. valueAsFreezableClone.GetAsFrozenCore(valueAsFreezable); } sourceValue = valueAsFreezableClone; Debug_VerifyCloneCommon(valueAsFreezable, valueAsFreezableClone, /*isDeepClone=*/ false); } } } SetValue(dp, sourceValue); } } } // Throws if owner/child are not context free and on different dispatchers. private static void EnsureConsistentDispatchers(DependencyObject owner, DependencyObject child) { Debug.Assert(owner != null && child != null, "Caller should guard against passing null owner/child."); // It is illegal to set a DependencyObject from one Dispatcher into a owner // being serviced by a different Dispatcher (i.e., they need to be on // the same thread or be context free (Dispatcher == null)) if (owner.Dispatcher != null && child.Dispatcher != null && owner.Dispatcher != child.Dispatcher) { throw new InvalidOperationException( SR.Get(SRID.Freezable_AttemptToUseInnerValueWithDifferentThread)); } } // These methods provide an abstraction for managing Freezable context // information - the context information being DO/DP pairs that the Freezable maps to. // // The methods will attempt to use as little memory as possible to store this information. // When there is only one context it will store the information directly, otherwise it will // place it within a list. When using a list, these methods place the DO/DP pairs so // that DOs are grouped together. This is done so that when walking the graph, it // is easier to track which DO's change handlers have already been gathered. // ////// Removes the context information for a Freezable. /// The DependencyObject to remove that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. /// private void RemoveContextInformation(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); bool failed = true; if (Freezable_UsingSingletonContext) { if (SingletonContext == context && SingletonContextProperty == property) { RemoveSingletonContext(); failed = false; } } else if (Freezable_UsingContextList) { FrugalObjectListlist = ContextList; int deadRefs = 0; int index = -1; int count = list.Count; for (int i = 0; i < count; i++) { FreezableContextPair entry = list[i]; object owner = entry.Owner.Target; if (owner != null) { if (failed && entry.Property == property && owner == context) { index = i; failed = false; } } else { ++deadRefs; } } if (index != -1) { Debug.Assert(!failed); list.RemoveAt(index); } PruneContexts(list, deadRefs); } // Make sure we actually removed something - if not throw an exception if (failed) { throw new ArgumentException(SR.Get(SRID.Freezable_NotAContext), "context"); } } /// /// Removes the single piece of contextual information that we have and updates all flags /// accordingly. /// private void RemoveSingletonContext() { Debug.Assert(Freezable_UsingSingletonContext); Debug.Assert(SingletonContext != null); if (HasHandlers) { _contextStorage = ((HandlerContextStorage)_contextStorage)._handlerStorage; } else { _contextStorage = null; } Freezable_UsingSingletonContext = false; } ////// Removes the context list and updates all flags accordingly. /// private void RemoveContextList() { Debug.Assert(Freezable_UsingContextList); if (HasHandlers) { _contextStorage = ((HandlerContextStorage)_contextStorage)._handlerStorage; } else { _contextStorage = null; } Freezable_UsingContextList = false; } ////// Helper function to add context information to a Freezable. /// /// The DependencyObject to add that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. internal override void AddInheritanceContext(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); // NTRAID#Longhorn-1677305-2006/05/25-[....] - Fix TemplateApplicationHelper::SetDependencyValueCore // // Debug_VerifyContextIsValid(context, property); if (!IsFrozenInternal) { DependencyObject oldInheritanceContext = InheritanceContext; AddContextInformation(context, property); // Check if the context has changed // If we are frozen, or we already had multiple contexts, the context has not changed if (oldInheritanceContext != InheritanceContext) { OnInheritanceContextChanged(EventArgs.Empty); } } } ////// Helper function to remove context information from a Freezable. /// /// The DependencyObject that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. internal override void RemoveInheritanceContext(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); if (!IsFrozenInternal) { DependencyObject oldInheritanceContext = InheritanceContext; RemoveContextInformation(context, property); // Check if the context has changed // If we are frozen, or we already had multiple contexts, the context has not changed if (oldInheritanceContext != InheritanceContext) { OnInheritanceContextChanged(EventArgs.Empty); } } } ////// Adds context information to a Freezable. /// The DependencyObject to add that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. /// internal void AddContextInformation(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); if (Freezable_UsingSingletonContext) { ConvertToContextList(); } if (Freezable_UsingContextList) { AddContextToList(context, property); } else { AddSingletonContext(context, property); } } ////// Helper function to convert to using a list to store context information. /// The SingletonContext is inserted into the list. /// private void ConvertToContextList() { Debug.Assert(Freezable_UsingSingletonContext); // The list is initialized with capacity for 2 entries since we // know we have a 2nd context to insert, hence the conversion // from the singleton context state. FrugalObjectListlist = new FrugalObjectList (2); // Note: This converts the SingletonContext from a strong reference to a WeakReference list.Add(new FreezableContextPair(SingletonContext, SingletonContextProperty)); if (HasHandlers) { ((HandlerContextStorage)_contextStorage)._contextStorage = list; } else { _contextStorage = list; } Freezable_UsingContextList = true; Freezable_UsingSingletonContext = false; // clear the singleton context property _property = null; } /// /// Helper function to add a singleton context to the Freezable's storage /// The DependencyObject to add that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. /// private void AddSingletonContext(DependencyObject context, DependencyProperty property) { Debug.Assert(!Freezable_UsingSingletonContext && !Freezable_UsingContextList); Debug.Assert(context != null); if (HasHandlers) { HandlerContextStorage hps = new HandlerContextStorage(); hps._handlerStorage = _contextStorage; hps._contextStorage = context; _contextStorage = hps; } else { _contextStorage = context; } // set the singleton context property _property = property; Freezable_UsingSingletonContext = true; } ////// Adds the context information to the context list. It does this by inserting the /// new context information in a location so that all context information referring /// to the same DO are grouped together. /// /// The DependencyObject to add that references this Freezable. /// The property of the DependencyObject this object maps to or null if none. private void AddContextToList(DependencyObject context, DependencyProperty property) { Debug.Assert(context != null); FrugalObjectListlist = ContextList; int count = list.Count; int insertIndx = count; // insert at the end by default int deadRefs = 0; DependencyObject lastContext = null; bool multipleInheritanceContextsFound = HasMultipleInheritanceContexts; // We can never leave this state once there // bool newInheritanceContext = context.CanBeInheritanceContext && !this.IsInheritanceContextSealed; // becomes false if we find context on the list for (int i = 0; i < count; i++) { DependencyObject currentContext = (DependencyObject)list[i].Owner.Target; if (currentContext != null) { if (currentContext == context) { // insert after the last matching context insertIndx = i + 1; newInheritanceContext = false; } if (newInheritanceContext && !multipleInheritanceContextsFound) { if (currentContext != lastContext && currentContext.CanBeInheritanceContext) // Count remaining inheritance contexts { // We already found a previous inheritance context, so we have multiple ones multipleInheritanceContextsFound = true; Freezable_HasMultipleInheritanceContexts = true; } lastContext = currentContext; } } else { ++deadRefs; } } list.Insert(insertIndx, new FreezableContextPair(context, property)); PruneContexts(list, deadRefs); } private void PruneContexts(FrugalObjectList oldList, int numDead) { int count = oldList.Count; if (count - numDead == 0) { RemoveContextList(); } else if (numDead > 0) { FrugalObjectList newList = new FrugalObjectList (count - numDead); for (int i = 0; i < count; i++) { if (oldList[i].Owner.IsAlive) { newList.Add(oldList[i]); } } ContextList = newList; } } /// /// Helper function to get all of the event handlers for the Freezable and /// place them in the calledHandlers list. /// Where to place the change handlers for the Freezable. /// private void GetHandlers(ref EventStorage calledHandlers) { if (Freezable_UsingSingletonHandler) { if (calledHandlers == null) { calledHandlers = GetEventStorage(); } calledHandlers.Add(SingletonHandler); } else if (Freezable_UsingHandlerList) { if (calledHandlers == null) { calledHandlers = GetEventStorage(); } FrugalObjectListhandlers = HandlerList; for (int i = 0, count = handlers.Count; i < count; i++) { calledHandlers.Add(handlers[i]); } } } /// /// Add the specified EventHandler /// /// Handler to add private void HandlerAdd(EventHandler handler) { Debug.Assert(handler != null); if (Freezable_UsingSingletonHandler) { ConvertToHandlerList(); } if (Freezable_UsingHandlerList) { HandlerList.Add(handler); } else { AddSingletonHandler(handler); } } ////// Remove the specified EventHandler /// /// Handler to remove private void HandlerRemove(EventHandler handler) { bool failed = true; Debug.Assert(handler != null); if (Freezable_UsingSingletonHandler) { if (SingletonHandler == handler) { RemoveSingletonHandler(); failed = false; } } else if (Freezable_UsingHandlerList) { FrugalObjectListhandlers = HandlerList; int index = handlers.IndexOf(handler); if (index >= 0) { handlers.RemoveAt(index); failed = false; } if (handlers.Count == 0) { RemoveHandlerList(); } } if (failed) { throw new ArgumentException(SR.Get(SRID.Freezable_UnregisteredHandler), "handler"); } } // // Removes the singleton handler the Freezable is storing and resets // any state indicating this. // private void RemoveSingletonHandler() { Debug.Assert(Freezable_UsingSingletonHandler); if (HasContextInformation) { _contextStorage = ((HandlerContextStorage)_contextStorage)._contextStorage; } else { _contextStorage = null; } Freezable_UsingSingletonHandler = false; } // // Removes the handler list the Freezable is storing and resets // any state indicating this. // private void RemoveHandlerList() { Debug.Assert(Freezable_UsingHandlerList && HandlerList.Count == 0); if (HasContextInformation) { _contextStorage = ((HandlerContextStorage)_contextStorage)._contextStorage; } else { _contextStorage = null; } Freezable_UsingHandlerList = false; } /// /// Helper function to convert to using a list to store context information. /// The SingletonContext is inserted into the list. /// private void ConvertToHandlerList() { Debug.Assert(Freezable_UsingSingletonHandler); EventHandler singletonHandler = SingletonHandler; // The list is initialized with capacity for 2 entries since we // know we have a 2nd handler to insert, hence the conversion // from the singleton handler state. FrugalObjectListlist = new FrugalObjectList (2); list.Add(singletonHandler); if (HasContextInformation) { ((HandlerContextStorage)_contextStorage)._handlerStorage = list; } else { _contextStorage = list; } Freezable_UsingHandlerList = true; Freezable_UsingSingletonHandler = false; } // // helper function to add a singleton handler. The passed in handler parameter // will be stored as the singleton handler. // private void AddSingletonHandler(EventHandler handler) { Debug.Assert(!Freezable_UsingHandlerList && !Freezable_UsingSingletonHandler); Debug.Assert(handler != null); if (HasContextInformation) { HandlerContextStorage hps = new HandlerContextStorage(); hps._contextStorage = _contextStorage; hps._handlerStorage = handler; _contextStorage = hps; } else { _contextStorage = handler; } Freezable_UsingSingletonHandler = true; } #endregion #region Private properties //----------------------------------------------------- // // Private properties // //------------------------------------------------------ // // The below properties help at getting the singleton/list for the context or change handlers. // In all cases, if the other object exists (i.e. we want context, and there are also stored handlers), // then _contextStorage is HandlerContextStorage, so we need to get the data we want from that class. // /// /// Returns the context list the Freezable has. This function assumes /// that UsingContextList is true before being called. /// private FrugalObjectListContextList { get { Debug.Assert(Freezable_UsingContextList && !Freezable_UsingSingletonContext, "Must call UsingContextList before use"); if (HasHandlers) { HandlerContextStorage ptrStorage = (HandlerContextStorage)_contextStorage; return (FrugalObjectList )ptrStorage._contextStorage; } else { return (FrugalObjectList )_contextStorage; } } set { Debug.Assert(Freezable_UsingContextList && !Freezable_UsingSingletonContext, "Must call UsingContextList before use"); if (HasHandlers) { ((HandlerContextStorage)_contextStorage)._contextStorage = value; } else { _contextStorage = value; } } } /// /// Returns the handler list the Freezable has. This function assumes /// the handlers for the Freezable are stored in a list. /// private FrugalObjectListHandlerList { get { Debug.Assert(Freezable_UsingHandlerList && !Freezable_UsingSingletonHandler, "Must call UsingHandlerList before use"); if (HasContextInformation) { HandlerContextStorage ptrStorage = (HandlerContextStorage)_contextStorage; return (FrugalObjectList )ptrStorage._handlerStorage; } else { return (FrugalObjectList )_contextStorage; } } } /// /// Returns the singleton handler the Freezable has. This function assumes /// that UsingSingletonHandler is true before being called. /// private EventHandler SingletonHandler { get { Debug.Assert(Freezable_UsingSingletonHandler && !Freezable_UsingHandlerList, "Must call UsingSingletonHandler before use"); if (HasContextInformation) { HandlerContextStorage ptrStorage = (HandlerContextStorage)_contextStorage; return (EventHandler)ptrStorage._handlerStorage; } else { return (EventHandler)_contextStorage; } } } ////// Returns the singleton context the Freezable has. This function assumes /// that UsingSingletonContext is true before being called. /// private DependencyObject SingletonContext { get { Debug.Assert(Freezable_UsingSingletonContext && !Freezable_UsingContextList, "Must call UsingSingletonContext before use"); if (HasHandlers) { HandlerContextStorage ptrStorage = (HandlerContextStorage)_contextStorage; return (DependencyObject)ptrStorage._contextStorage; } else { return (DependencyObject)_contextStorage; } } } ////// Returns/sets the singleton context property of the Freezable. This /// function assumes that UsingSingletonContext is true before being called. /// private DependencyProperty SingletonContextProperty { get { Debug.Assert(Freezable_UsingSingletonContext && !Freezable_UsingContextList, "Must call UsingSingletonContext before use"); return (DependencyProperty)_property; } } ////// Whether the Freezable has event handlers. /// private bool HasHandlers { get { return (Freezable_UsingHandlerList || Freezable_UsingSingletonHandler); } } ////// Whether the Freezable has context information. /// private bool HasContextInformation { get { return (Freezable_UsingContextList || Freezable_UsingSingletonContext); } } #endregion #region InheritanceContext ////// InheritanceContext /// internal override DependencyObject InheritanceContext { [FriendAccessAllowed] // Built into Base, also used by Core and Framework. get { if (!Freezable_HasMultipleInheritanceContexts) { if (Freezable_UsingSingletonContext) // We have exactly one Freezable context { DependencyObject singletonContext = SingletonContext; if (singletonContext.CanBeInheritanceContext) { return singletonContext; } } else if (Freezable_UsingContextList) { // We have multiple Freezable contexts, but at most one context is valid FrugalObjectListlist = ContextList; int count = list.Count; for (int i = 0; i < count; i++) { DependencyObject currentContext = (DependencyObject)list[i].Owner.Target; if (currentContext != null && currentContext.CanBeInheritanceContext) { // This is the first and only valid inheritance context we should find return currentContext; } } } } return null; // If we have gotten here, we have either multiple or no valid contexts } } /// /// HasMultipleInheritanceContexts /// internal override bool HasMultipleInheritanceContexts { [FriendAccessAllowed] // Built into Base, also used by Core and Framework. get { return Freezable_HasMultipleInheritanceContexts; } } #endregion InheritanceContext // // A simple class that is used when the Freezable needs to store both handlers and context info. // The _handlerStorage and _contextStorage fields can store either a list or a direct // reference to the object - Freezable's Freezable_* flags (actually added to DependencyObject.cs) // can be used to test for which one to use. // private class HandlerContextStorage { public object _handlerStorage; public object _contextStorage; } // // A simple struct that stores a weak ref to a dependency object and a corresponding property // of that object. // private struct FreezableContextPair { public FreezableContextPair(DependencyObject dependObject, DependencyProperty dependProperty) { Owner = new WeakReference(dependObject); Property = dependProperty; } public readonly WeakReference Owner; public readonly DependencyProperty Property; } // // A simple class that is used to cache the event handlers that are gathered during a call // to FireChanged. Using this cache cuts down on the amount of managed allocations, which // improves the performance of Freezables. // private class EventStorage { public EventStorage(int initialSize) { // check just in case if (initialSize <= 0) initialSize = 1; _events = new EventHandler[initialSize]; _logSize = 0; _physSize = initialSize; _inUse = false; } // // Adds a new EventHandler to the storage. In the case that more memory is needed, the cache // size is doubled. // public void Add(EventHandler e) { if (_logSize == _physSize) { _physSize *= 2; EventHandler[] temp = new EventHandler[_physSize]; for (int i = 0; i < _logSize; i++) { temp[i] = _events[i]; } _events = temp; } _events[_logSize] = e; _logSize++; } // // Clears the list but does not free the memory so that future uses of the // class can reuse the space and not take an allocation performance hit. // public void Clear() { _logSize = 0; } public int Count { get { return _logSize; } } public int PhysicalSize { get { return _physSize; } } public EventHandler this[int idx] { get { return _events[idx]; } set { _events[idx] = value; } } // // So that it's possible to reuse EventStorage classes, and so that if one is being used, another // person does not overwrite the contents (i.e. FireChanged causes someone else call their FireChanged), // an InUse flag is set to indicate whether someone is currently using this class. // public bool InUse { get { return _inUse; } set { _inUse = value; } } EventHandler[] _events; // list of events int _logSize; // the logical size of the list int _physSize; // the allocated buffer size bool _inUse; } //----------------------------------------------------- // // Debug fields // //------------------------------------------------------ #region Debug // Verify a clone. If isDeepClone is true we make sure that the cloned object is not the same as the // original. GetAsFrozen and GetCurrentValueAsFrozen do not do deep clones since they will immediately // return any frozen originals rather than cloning them. private static void Debug_VerifyCloneCommon(Freezable original, object clone, bool isDeepClone) { if (Invariant.Strict) { Freezable cloneAsFreezable = (Freezable) clone; Debug_VerifyInstance("CloneCore", original, cloneAsFreezable); // Extra CloneCommon checks if (isDeepClone) { Invariant.Assert(clone != original, "CloneCore should not return the same instance as the original."); } Invariant.Assert(!cloneAsFreezable.HasHandlers, "CloneCore should not have handlers attached on construction."); IList originalAsIList = original as IList; if (originalAsIList != null) { // we've already checked that original and clone are the same type IList cloneAsIList = clone as IList; Invariant.Assert(originalAsIList.Count == cloneAsIList.Count, "CloneCore didn't clone all of the elements in the list."); for (int i = 0; i < cloneAsIList.Count; i++) { Freezable originalItemAsFreezable = originalAsIList[i] as Freezable; Freezable cloneItemAsFreezable = cloneAsIList[i] as Freezable; if (isDeepClone && cloneItemAsFreezable != null && cloneItemAsFreezable != null) { Invariant.Assert(originalItemAsFreezable != cloneItemAsFreezable, "CloneCore didn't clone the elements in the list correctly."); } } } } } private static void Debug_VerifyInstance(String methodName, Freezable original, Freezable newInstance) { if (Invariant.Strict) { Invariant.Assert(newInstance != null, "{0} should not return null.", methodName); Invariant.Assert(newInstance.GetType() == original.GetType(), String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} should return instance of same type. (Expected= '{1}', Actual='{2}')", methodName, original.GetType(), newInstance.GetType())); Invariant.Assert(!newInstance.IsFrozen, "{0} should return a mutable instance. Recieved a frozen instance.", methodName); } } // Enumerates our FreezableContextPairs and (when we have full DP information) // verifies that the context is still valid. private void Debug_DetectContextLeaks() { if (Invariant.Strict) { if (Freezable_UsingSingletonContext) { Debug_VerifyContextIsValid(SingletonContext, SingletonContextProperty); } else if (Freezable_UsingContextList) { FrugalObjectListcontextList = ContextList; for(int i = 0, count = ContextList.Count; i < count; i++) { FreezableContextPair context = ContextList[i]; DependencyObject owner = (DependencyObject) context.Owner.Target; if (!context.Owner.IsAlive) { // If the WeakReference is no longer alive the owner which // was "using" this Freezable has been GC'ed. // // There is no way to verify that this object was pointing // to us pre-collection, but in theory it was and we are // just waiting for compaction. continue; } Debug_VerifyContextIsValid(owner, context.Property); } } } } // Verifies that the given owner/property pair constitutes a valid // inheritance context for this Freezable. This is a no-op if the // property is null. private void Debug_VerifyContextIsValid(DependencyObject owner, DependencyProperty property) { if (Invariant.Strict) { Invariant.Assert(owner != null, "We should not have null owners in the ContextList/SingletonContext."); if (property == null) { // This context was not made through a DependencyProperty. There is // nothing we can verify. return; } // If we have DP information for the context, we can verify that // the property on the owner is still referencing us. Example: // // (Pen.Brush) // // .-----. // ' v // Pen Brush // ^ . // '-----' // // Context // // If the owner's DP value does not point to us than we've leaked // a context. DependencyObject ownerAsDO = (DependencyObject) owner; object effectiveValue = ownerAsDO.GetValue(property); // There is a notable exception to the rule above, which is that // ResourceDictionaries create a context between the resource and // the FE which owns the resource. // // In this case, the connection will be made via the pragmatic, // but somewhat arbitrarily chosen VisualBrush.Visual DP. // // See comments in ResourceDictionary.AddInheritanceContext. Note // that the owner will be the FE which owns the ResourceDictionary, // not the ResourceDictionary itself. bool mayBeResourceDictionary = property.Name == "Visual" && property.OwnerType.FullName == "System.Windows.Media.VisualBrush" && owner.GetType().FullName != "System.Windows.Media.VisualBrush"; // ResourceDictionaries may not be owned by a VisualBrush. // NTRAID#Longhorn-1692587-2006/06/06-[....] - Find a way to bring back context verification. // // Invariant.Assert(effectiveValue == this || mayBeResourceDictionary, // String.Format(System.Globalization.CultureInfo.InvariantCulture, // "Detected context leak: Property '{0}.{1}' on {2}. Expected '{3}', Actual '{4}'", // property.OwnerType.Name, // property.Name, // owner.GetType().FullName, // this, // effectiveValue)); } } #endregion Debug //------------------------------------------------------ // // Private fields // //----------------------------------------------------- #region Private Fields // For the common case of having only a single context, we use _property // to store the DependencyProperty that goes with the single context. private DependencyProperty _property; // initial size to make the EventStorage cache private const int INITIAL_EVENTSTORAGE_SIZE = 4; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ReversePositionQuery.cs
- SearchForVirtualItemEventArgs.cs
- backend.cs
- MonitoringDescriptionAttribute.cs
- DocumentCollection.cs
- HashUtility.cs
- GridViewDeletedEventArgs.cs
- SystemColorTracker.cs
- BlockUIContainer.cs
- ObjectDataSourceView.cs
- ReadContentAsBinaryHelper.cs
- RenamedEventArgs.cs
- WebServiceReceive.cs
- EdmConstants.cs
- RowUpdatedEventArgs.cs
- WebPartTransformerAttribute.cs
- ComponentFactoryHelpers.cs
- ConfigXmlSignificantWhitespace.cs
- DataComponentNameHandler.cs
- XmlILStorageConverter.cs
- CompiledAction.cs
- SafeIUnknown.cs
- InputReport.cs
- ParseChildrenAsPropertiesAttribute.cs
- DateTimeSerializationSection.cs
- HttpContext.cs
- _HeaderInfo.cs
- DashStyle.cs
- PropertyGroupDescription.cs
- CertificateElement.cs
- ThemeConfigurationDialog.cs
- DataSourceXmlTextReader.cs
- NumberFormatter.cs
- ArcSegment.cs
- DefaultTraceListener.cs
- BmpBitmapEncoder.cs
- ReceiveCompletedEventArgs.cs
- ConfigurationSection.cs
- Propagator.ExtentPlaceholderCreator.cs
- SliderAutomationPeer.cs
- ICollection.cs
- OutArgument.cs
- IdentityManager.cs
- FamilyTypeface.cs
- Lasso.cs
- SoapSchemaMember.cs
- EventArgs.cs
- FixedTextBuilder.cs
- OdbcConnectionPoolProviderInfo.cs
- ColorTransformHelper.cs
- MachineSettingsSection.cs
- DnsPermission.cs
- PresentationAppDomainManager.cs
- X509CertificateValidationMode.cs
- SHA1.cs
- EntityDataSourceContainerNameConverter.cs
- SQLBinaryStorage.cs
- SystemIPv6InterfaceProperties.cs
- RepeaterItem.cs
- WebServiceHandler.cs
- DPCustomTypeDescriptor.cs
- SoapIgnoreAttribute.cs
- DefaultSection.cs
- VectorAnimation.cs
- DBCSCodePageEncoding.cs
- IChannel.cs
- UriSectionData.cs
- DriveNotFoundException.cs
- CommonRemoteMemoryBlock.cs
- Matrix3DValueSerializer.cs
- SchemaImporter.cs
- ColumnMap.cs
- CapabilitiesAssignment.cs
- ArraySortHelper.cs
- SmiContextFactory.cs
- MimePart.cs
- MasterPageCodeDomTreeGenerator.cs
- DisposableCollectionWrapper.cs
- InkPresenterAutomationPeer.cs
- InputBuffer.cs
- TdsParserHelperClasses.cs
- CollectionContainer.cs
- SID.cs
- AbsoluteQuery.cs
- SmtpFailedRecipientsException.cs
- PeerApplication.cs
- DataControlLinkButton.cs
- ConfigurationLocation.cs
- AttachmentCollection.cs
- UnsafeNativeMethods.cs
- TCEAdapterGenerator.cs
- ScriptResourceInfo.cs
- WindowsFormsSectionHandler.cs
- EventToken.cs
- FocusWithinProperty.cs
- SystemDiagnosticsSection.cs
- BasicExpressionVisitor.cs
- OutputScopeManager.cs
- OdbcParameter.cs
- InstallerTypeAttribute.cs