private static IEnumerable <Action <object, DependencyContainer, CacheInfo> > createMemberActivator(MemberInfo member, Type type, bool allowValueTypes) { switch (member) { case PropertyInfo pi: { var getMethod = pi.GetMethod; if (getMethod == null) { throw new AccessModifierNotAllowedForCachedValueException(AccessModifier.None, pi); } if (getMethod.GetCustomAttribute <CompilerGeneratedAttribute>() == null) { throw new AccessModifierNotAllowedForCachedValueException(AccessModifier.None, pi); } var setMethod = pi.SetMethod; if (setMethod != null) { var modifier = setMethod.GetAccessModifier(); if (modifier != AccessModifier.Private) { throw new AccessModifierNotAllowedForCachedValueException(modifier, setMethod); } if (setMethod.GetCustomAttribute <CompilerGeneratedAttribute>() == null) { throw new AccessModifierNotAllowedForCachedValueException(AccessModifier.None, pi); } } break; } case FieldInfo fi: { var modifier = fi.GetAccessModifier(); if (modifier != AccessModifier.Private && !fi.IsInitOnly) { throw new AccessModifierNotAllowedForCachedValueException(modifier, fi); } break; } } foreach (var attribute in member.GetCustomAttributes <CachedAttribute>()) { yield return((target, dc, info) => { object value = null; if (member is PropertyInfo p) { value = p.GetValue(target); } if (member is FieldInfo f) { value = f.GetValue(target); } if (value == null) { if (allowValueTypes) { return; } throw new NullReferenceException($"Attempted to cache a null value: {type.ReadableName()}.{member.Name}."); } var cacheInfo = new CacheInfo(info.Name ?? attribute.Name); if (info.Parent != null) { // When a parent type exists, infer the property name if one is not provided cacheInfo = new CacheInfo(cacheInfo.Name ?? member.Name, info.Parent); } dc.CacheAs(attribute.Type ?? value.GetType(), cacheInfo, value, allowValueTypes); }); } }
/// <summary> /// Retrieves a cached dependency of type <typeparamref name="T"/> if it exists, and null otherwise. /// </summary> /// <typeparam name="T">The dependency type to query for.</typeparam> /// <param name="container">The <see cref="IReadOnlyDependencyContainer"/> to query.</param> /// <param name="info">Extra information that identifies the cached dependency.</param> /// <returns>The requested dependency, or null if not found.</returns> public static T Get <T>(this IReadOnlyDependencyContainer container, CacheInfo info) where T : class => (T)container.Get(typeof(T), info);
/// <summary> /// Caches an instance of a type as its most derived type. This instance will be returned each time you <see cref="Get(Type)"/>. /// </summary> /// <param name="instance">The instance to cache.</param> /// <param name="info">Extra information to identify <paramref name="instance"/> in the cache.</param> public void Cache(object instance, CacheInfo info) => CacheAs(instance.GetType(), info, instance, false);
/// <summary> /// Caches an instance of a type as a type of <paramref name="type"/>. This instance will be returned each time you <see cref="Get(Type)"/>. /// </summary> /// <param name="type">The type to cache <paramref name="instance"/> as.</param> /// <param name="instance">The instance to cache. Must be or derive from <paramref name="type"/>.</param> /// <param name="info">Extra information to identify <paramref name="instance"/> in the cache.</param> public void CacheAs <T>(Type type, T instance, CacheInfo info) where T : class => CacheAs(type, info, instance, false);
public TypeAlreadyCachedException(CacheInfo info) : base($"An instance of the member ({info.ToString()}) has already been cached to the dependency container.") { }
/// <summary> /// Caches an instance of a type as a type of <typeparamref name="T"/>. This instance will be returned each time you <see cref="DependencyContainer.Get(Type)"/>. /// </summary> /// <remarks> /// This should only be used when it is guaranteed that the internal state of the type will remain consistent through retrieval. /// (e.g. <see cref="CancellationToken"/> or reference types). /// </remarks> /// <param name="instance">The instance to cache. Must be or derive from <typeparamref name="T"/>.</param> /// <param name="info">Extra information to identify <paramref name="instance"/> in the cache.</param> internal void CacheValueAs <T>(T instance, CacheInfo info) => CacheAs(typeof(T), info, instance, true);
private static Func <IReadOnlyDependencyContainer, object> getDependency(Type type, Type requestingType, bool permitNulls, CacheInfo info) => dc => { var val = dc.Get(type, info); if (val == null && !permitNulls) { throw new DependencyNotRegisteredException(requestingType, type); } if (val is IBindable bindableVal) { return(bindableVal.GetBoundCopy()); } return(val); };
private IReadOnlyDependencyContainer mergeDependencies(object obj, IReadOnlyDependencyContainer dependencies, CacheInfo info) { dependencies = baseActivator?.mergeDependencies(obj, dependencies, info) ?? dependencies; foreach (var a in buildCacheActivators) { dependencies = a(obj, dependencies, info); } return(dependencies); }
/// <summary> /// Merges existing dependencies with new dependencies from an object into a new <see cref="IReadOnlyDependencyContainer"/>. /// </summary> /// <param name="obj">The object whose dependencies should be merged into the dependencies provided by <paramref name="dependencies"/>.</param> /// <param name="dependencies">The existing dependencies.</param> /// <param name="info">Extra information to identify parameters of <paramref name="obj"/> in the cache with.</param> /// <returns>A new <see cref="IReadOnlyDependencyContainer"/> if <paramref name="obj"/> provides any dependencies, otherwise <paramref name="dependencies"/>.</returns> public static IReadOnlyDependencyContainer MergeDependencies(object obj, IReadOnlyDependencyContainer dependencies, CacheInfo info = default) => getActivator(obj.GetType()).mergeDependencies(obj, dependencies, info);