/// <summary>
        /// Adds a watcher for the specified asset.
        /// </summary>
        /// <param name="asset">The asset identifier of the asset for which to add a watcher.</param>
        /// <param name="watcher">The watcher to add for the specified asset.</param>
        /// <returns><see langword="true"/> if the watcher was added; otherwise, <see langword="false"/>.</returns>
        public Boolean AddWatcher <TOutput>(AssetID asset, AssetWatcher <TOutput> watcher)
        {
            Contract.Require(watcher, nameof(watcher));

            var primaryDisplay = ContentManager.Ultraviolet.GetPlatform().Displays.PrimaryDisplay;
            var primaryDisplayDensityBucket = primaryDisplay?.DensityBucket ?? ScreenDensityBucket.Desktop;

            return(AddWatcherInternal(AssetID.GetAssetPath(asset), primaryDisplayDensityBucket, watcher));
        }
        /// <summary>
        /// Removes a watcher from the specified asset.
        /// </summary>
        /// <param name="asset">The asset path of the asset for which to remove a watcher.</param>
        /// <param name="watcher">The watcher to remove from the specified asset.</param>
        /// <returns><see langword="true"/> if the specified watcher was removed; otherwise, <see langword="false"/>.</returns>
        public Boolean RemoveWatcher <TOutput>(String asset, AssetWatcher <TOutput> watcher)
        {
            Contract.Require(asset, nameof(asset));
            Contract.Require(watcher, nameof(watcher));

            var primaryDisplay = ContentManager.Ultraviolet.GetPlatform().Displays.PrimaryDisplay;
            var primaryDisplayDensityBucket = primaryDisplay?.DensityBucket ?? ScreenDensityBucket.Desktop;

            return(RemoveWatcherInternal(asset, primaryDisplayDensityBucket, watcher));
        }
        /// <summary>
        /// Removes an asset watcher from the collection.
        /// </summary>
        /// <param name="watcher">The asset watcher to remove from the collection.</param>
        /// <returns><see langword="true"/> if the asset watcher was removed; otherwise, <see langword="false"/>.</returns>
        public bool Remove(AssetWatcher <T> watcher)
        {
            Contract.Require(watcher, nameof(watcher));

            return(storage.Remove(watcher));
        }
        /// <summary>
        /// Adds an asset watcher to the collection.
        /// </summary>
        /// <param name="watcher">The asset watcher to add to the collection.</param>
        public void Add(AssetWatcher <T> watcher)
        {
            Contract.Require(watcher, nameof(watcher));

            storage.Add(watcher);
        }
        /// <summary>
        /// Adds a watcher for the specified asset.
        /// </summary>
        /// <param name="asset">The asset identifier of the asset for which to add a watcher.</param>
        /// <param name="density">The density bucket corresponding to the version of the asset to watch.</param>
        /// <param name="watcher">The watcher to add for the specified asset.</param>
        /// <returns><see langword="true"/> if the watcher was added; otherwise, <see langword="false"/>.</returns>
        public Boolean AddWatcher <TOutput>(AssetID asset, ScreenDensityBucket density, AssetWatcher <TOutput> watcher)
        {
            Contract.Require(watcher, nameof(watcher));

            return(AddWatcherInternal(AssetID.GetAssetPath(asset), density, watcher));
        }
        /// <summary>
        /// Adds a watcher for the specified asset.
        /// </summary>
        /// <param name="asset">The asset path of the asset for which to add a watcher.</param>
        /// <param name="density">The density bucket corresponding to the version of the asset to watch.</param>
        /// <param name="watcher">The watcher to add for the specified asset.</param>
        /// <returns><see langword="true"/> if the watcher was added; otherwise, <see langword="false"/>.</returns>
        public Boolean AddWatcher <TOutput>(String asset, ScreenDensityBucket density, AssetWatcher <TOutput> watcher)
        {
            Contract.Require(asset, nameof(asset));
            Contract.Require(watcher, nameof(watcher));

            return(AddWatcherInternal(asset, density, watcher));
        }
        /// <summary>
        /// Removes a watcher from the specified asset.
        /// </summary>
        private Boolean RemoveWatcherInternal <TOutput>(String asset, ScreenDensityBucket density, AssetWatcher <TOutput> watcher)
        {
            lock (ContentManager.AssetCache.SyncObject)
            {
                if (assetWatchers == null)
                {
                    return(false);
                }

                if (assetWatchers.TryGetValue(asset, out var list))
                {
                    return(list.Remove(watcher));
                }

                return(false);
            }
        }
        /// <summary>
        /// Adds a watcher to the specified asset.
        /// </summary>
        private Boolean AddWatcherInternal <TOutput>(String asset, ScreenDensityBucket density, AssetWatcher <TOutput> watcher)
        {
            lock (ContentManager.AssetCache.SyncObject)
            {
                if (CreateFileSystemWatchers())
                {
                    if (assetWatchers == null)
                    {
                        assetWatchers = new Dictionary <String, IAssetWatcherCollection>();
                    }

                    var assetPath         = asset;
                    var assetResolvedPath = ContentManager.ResolveAssetFilePath(assetPath, density, true);
                    var assetFilePath     = Path.GetFullPath(assetResolvedPath);

                    if (!assetWatchers.TryGetValue(assetFilePath, out var list))
                    {
                        list = new AssetWatcherCollection <TOutput>(ContentManager, assetPath, assetFilePath);
                        assetWatchers[assetFilePath] = list;
                    }

                    list.Add(watcher);

                    return(true);
                }

                return(false);
            }
        }