using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Chernobyl.Collections.Generic.Event; namespace Chernobyl.Dependency { /// /// A base class for s that are used to work with /// services. /// public abstract class ServiceAttribute : Attribute { /// /// Initializes a new instance of the /// class. /// protected ServiceAttribute() : this(Array.Empty) {} /// /// Initializes a new instance of the /// class. /// /// The location of the service within the /// hierarchy or an empty array if the service /// is at the root of the hierarchy. This array /// represents the "path" that must be taken through the hierarchy of /// to get to the location where the service /// specified by resides. Each /// must implement the /// interface. An example of what the service path might look like /// (using '/' as separators): /// /IEventCollection{Object}/IGraphicsServices/ITextureServices /// In this example, the service represented by /// is located in the ITextureServices. That /// is located within IGraphicsServices which /// is located at the root . protected ServiceAttribute(params Type[] location) { Location = location; } /// /// Performs the intended goal of this /// with relation to the attributed code element /// () and the /// () that is processing that code element. /// /// The instance that /// allows access to the other services and provides adding or removing /// of specific services. /// The instance that contained a code element that /// was attributed with a derived instance. /// The code element that was attributed /// with a derived instance. public abstract void Resolve(IEventCollection services, object instance, ICustomAttributeProvider attributeProvider); /// /// The location of the service within the /// hierarchy or an empty array if the service is at the root of the /// hierarchy. This array represents the "path" /// that must be taken through the hierarchy of /// to get to the location where the service specified by /// resides. Each must /// implement the interface. An example of /// what the service path might look like (using '/' as separators): /// /IEventCollection{Object}/IGraphicsServices/ITextureServices /// In this example, the service represented by /// is located in the ITextureServices. That /// is located within IGraphicsServices which /// is located at the root . /// /// Thrown when any of the /// s contained within the array set on this property /// are not of the . public Type[] Location { get { return _location; } set { if(value.All(type => typeof(IEventCollection).IsAssignableFrom(type)) == false) throw new ArgumentException("All of the Types within the " + "Location must be of type \"" + typeof(IEventCollection).FullName + "\" to allow for a hierarchy of " + typeof(IEventCollection).FullName + "instances.", "value"); _location = value; } } /// /// The type of the service being provided or injected. Set /// this property if the property type, return type, or /// method parameter type does not depict the service type needed /// or provided. /// public Type Type { get; set; } /// /// The backing field to . /// Type[] _location; } /// /// Utility methods for or code that uses it. /// public static class ServiceAttributeExtensions { /// /// This method will set a service onto any member that has been /// attributed with the attribute when that /// service is ready. For any member that has been attributed with the /// attribute, this method will take the /// value of that member as service. By default, this method uses /// /// to locate the instances that /// are to have services injected into or pulled from them. /// /// The type of the object to take services /// from or give services to. /// The instance that will take or give services. /// The object that is to have services injected /// into or taken from it. public static void Inject(this IEventCollection services, T instance) { services.Inject(instance, DeclaredOnlyInstancePublicMembers); } /// /// This method will set a service onto any member that has been /// attributed with the attribute when that /// service is ready. For any member that has been attributed with the /// attribute, this method will take the /// value of that member as service. /// /// The type of the object to take services /// from or give services to. /// The instance that will take or give services. /// The object that is to have services injected /// into or taken from it. /// The method used to get the /// s on the /// . The /// s will be searched for /// s that will be used for injection. public static void Inject(this IEventCollection services, T instance, Func> attributeProviderRetriever) { Type objectType = typeof(T); IEnumerable attributeProviders = attributeProviderRetriever(objectType); foreach (ICustomAttributeProvider attributeProvider in attributeProviders) { object[] attributes = attributeProvider.GetCustomAttributes(typeof(ServiceAttribute), true); foreach (ServiceAttribute serviceAttribute in attributes.Cast()) serviceAttribute.Resolve(services, instance, attributeProvider); } } /// /// Returns the , /// , and /// /// instances of the passed in as /// s. /// /// The type whose /// s are to be retrieved. /// The of the /// as s. public static IEnumerable DeclaredOnlyInstancePublicMembers(Type type) { return type.GetProperties(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public); } } }