Files
Umbraco-CMS/src/Umbraco.Core/Models/PropertyGroup.cs
Shannon 4a3525ece3 Fixes a memory leak caused by deep cloning
There was a memory leak with PublicAccessEntry during even unassignment which was not clearing the correct handler.
This goes a step further and adds a new ClearCollectionChangedEvents method for all observable collections used in umbraco
which allows fully clearing ALL event handlers instead of having to track specific ones. This will ensure there are no
unintended memory leaks in case end-users have assigned event handlers to the collection changed event which would
not be unassigned during deep cloning.
2021-01-12 13:41:50 +11:00

113 lines
3.7 KiB
C#

using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// A group of property types, which corresponds to the properties grouped under a Tab.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
[DebuggerDisplay("Id: {Id}, Name: {Name}")]
public class PropertyGroup : EntityBase, IEquatable<PropertyGroup>
{
private string _name;
private int _sortOrder;
private PropertyTypeCollection _propertyTypes;
public PropertyGroup(bool isPublishing)
: this(new PropertyTypeCollection(isPublishing))
{ }
public PropertyGroup(PropertyTypeCollection propertyTypeCollection)
{
PropertyTypes = propertyTypeCollection;
}
private void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(PropertyTypes));
}
/// <summary>
/// Gets or sets the Name of the Group, which corresponds to the Tab-name in the UI
/// </summary>
[DataMember]
public string Name
{
get => _name;
set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
}
/// <summary>
/// Gets or sets the Sort Order of the Group
/// </summary>
[DataMember]
public int SortOrder
{
get => _sortOrder;
set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder));
}
/// <summary>
/// Gets or sets a collection of PropertyTypes for this PropertyGroup
/// </summary>
/// <remarks>
/// Marked DoNotClone because we will manually deal with cloning and the event handlers
/// </remarks>
[DataMember]
[DoNotClone]
public PropertyTypeCollection PropertyTypes
{
get => _propertyTypes;
set
{
if (_propertyTypes != null)
{
_propertyTypes.ClearCollectionChangedEvents();
}
_propertyTypes = value;
// since we're adding this collection to this group,
// we need to ensure that all the lazy values are set.
foreach (var propertyType in _propertyTypes)
propertyType.PropertyGroupId = new Lazy<int>(() => Id);
OnPropertyChanged(nameof(PropertyTypes));
_propertyTypes.CollectionChanged += PropertyTypesChanged;
}
}
public bool Equals(PropertyGroup other)
{
if (base.Equals(other)) return true;
return other != null && Name.InvariantEquals(other.Name);
}
public override int GetHashCode()
{
var baseHash = base.GetHashCode();
var nameHash = Name.ToLowerInvariant().GetHashCode();
return baseHash ^ nameHash;
}
protected override void PerformDeepClone(object clone)
{
base.PerformDeepClone(clone);
var clonedEntity = (PropertyGroup)clone;
if (clonedEntity._propertyTypes != null)
{
clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any
clonedEntity._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone
clonedEntity._propertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler
}
}
}
}