Last active
August 29, 2015 14:11
-
-
Save bfriesen/bbf37b35f2946daaa439 to your computer and use it in GitHub Desktop.
A general-purpose "DI container" for use by libraries to resolve static dependencies. Add a nuget reference to "InjectModuleInitializer".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
namespace PLACEHOLDER.StaticDependencyInjection | |
{ | |
internal partial class CompositionRoot | |
{ | |
public override void Bootstrap() | |
{ | |
// TODO: Add calls to the various Import methods. | |
} | |
protected override ExportInfo GetExportInfo(Type type) | |
{ | |
var attribute = Attribute.GetCustomAttribute(type, typeof(ExportAttribute)) as ExportAttribute; | |
if (attribute == null) | |
{ | |
return base.GetExportInfo(type); | |
} | |
return | |
new ExportInfo(type, attribute.Priority) | |
{ | |
Disabled = attribute.Disabled, | |
Name = attribute.Name | |
}; | |
} | |
protected override IEnumerable<ExportInfo> GetExportInfos( | |
IEnumerable<CustomAttributeData> assemblyAttributes) | |
{ | |
return | |
assemblyAttributes.AsAttributeType<ExportExternalAttribute>() | |
.Where(attribute => attribute.ClassType.IsClass) | |
.Select(attribute => | |
new ExportInfo(attribute.ClassType, attribute.Priority) | |
{ | |
Disabled = attribute.Disabled, | |
Name = attribute.Name | |
}); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////////////////////////////////////////////////////////////////////////////////////// | |
// // | |
// This file was generated by a tool. It would be a bad idea to make changes to it. // | |
// // | |
////////////////////////////////////////////////////////////////////////////////////// | |
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
namespace PLACEHOLDER.StaticDependencyInjection | |
{ | |
internal sealed partial class CompositionRoot : CompositionRootBase | |
{ | |
internal CompositionRoot() | |
{ | |
} | |
} | |
internal abstract class CompositionRootBase | |
{ | |
private readonly ConcurrentDictionary<string, ICollection<Type>> _candidateTypesCache; | |
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, IEnumerable<string>>> _candidateTypeNamesByTargetTypeNameCache; | |
protected CompositionRootBase() | |
{ | |
_candidateTypesCache = new ConcurrentDictionary<string, ICollection<Type>>(); | |
_candidateTypeNamesByTargetTypeNameCache = new ConcurrentDictionary<string, ConcurrentDictionary<string, IEnumerable<string>>>(); | |
} | |
/// <summary> | |
/// Import the types for this library by calling one of the import methods: | |
/// <see cref="ImportSingle{TTargetType}"/>, <see cref="ImportSingle{TTargetType,TFactoryType}"/>, | |
/// <see cref="ImportFirst{TTargetType}"/>, <see cref="ImportFirst{TTargetType,TFactoryType}"/>, | |
/// <see cref="ImportMultiple{TTargetType}"/>, or <see cref="ImportMultiple{TTargetType, TFactoryType}"/>. | |
/// </summary> | |
public abstract void Bootstrap(); | |
/// <summary> | |
/// Return a metadata object that describes the export operation for a type. | |
/// </summary> | |
/// <param name="type">The type to get export metadata.</param> | |
/// <returns>A metadata object that describes an export operation.</returns> | |
protected virtual ExportInfo GetExportInfo(Type type) | |
{ | |
return new ExportInfo(type); | |
} | |
/// <summary> | |
/// Return a collection of metadata objects that correspond to the attributes. | |
/// Use the <see cref="Extensions.AsAttributeType{TAttribute}"/> extension method | |
/// to convert applicable CustomAttributeData objects to the desired attribyte type. | |
/// </summary> | |
/// <param name="assemblyAttributes"> | |
/// The collection of attribute data describing attributes that decorate an assembly. | |
/// </param> | |
/// <returns>A collection of metadata objects that describe export operations.</returns> | |
protected virtual IEnumerable<ExportInfo> GetExportInfos( | |
IEnumerable<CustomAttributeData> assemblyAttributes) | |
{ | |
yield break; | |
} | |
/// <summary> | |
/// Return an object that defines various options. | |
/// </summary> | |
protected virtual ImportOptions GetDefaultImportOptions() | |
{ | |
return new ImportOptions(); | |
} | |
/// <summary> | |
/// Imports the type specified by <typeparamref name="TTargetType"/>. When a single | |
/// class with a public parameterless constructor is found that implements or | |
/// inherits from <typeparamref name="TTargetType"/>, then an instance of that class | |
/// will be created and passed to the <paramref name="importAction"/> parameter callback. | |
/// </summary> | |
/// <typeparam name="TTargetType"> | |
/// The type to import. An object of this type will be passed to the | |
/// <paramref name="importAction"/> parameter callback. | |
/// </typeparam> | |
/// <param name="importAction"> | |
/// A callback function to invoke when an implementation of <typeparamref name="TTargetType"/> is created. | |
/// </param> | |
/// <param name="importName"> | |
/// The name of this import operation. If not null, exported classes without a matching name are excluded. | |
/// </param> | |
/// <param name="options"> | |
/// The import options to use. If null or not provided, the value returned by | |
/// <see cref="GetDefaultImportOptions"/> is returned. | |
/// </param> | |
protected void ImportSingle<TTargetType>( | |
Action<TTargetType> importAction, | |
string importName = null, | |
ImportOptions options = null) | |
where TTargetType : class | |
{ | |
ImportSingleType( | |
importAction, | |
GetImportInfo<TTargetType>(importName, options), | |
CreateInstance<TTargetType>); | |
} | |
/// <summary> | |
/// Imports the type specified by <typeparamref name="TTargetType"/>. When a single | |
/// class with a public parameterless constructor is found that implements or | |
/// inherits from either <typeparamref name="TTargetType"/> or | |
/// <typeparamref name="TFactoryType"/>, then an instance of that class is created. | |
/// If that instance is a <see cref="TTargetType"/>, than that instance will be | |
/// passed to the <paramref name="importAction"/> callback. If the instance is a | |
/// <typeparamref name="TFactoryType"/>, then an instance of | |
/// <typeparamref name="TTargetType"/> is obtained by using the | |
/// <paramref name="getTarget"/> function and passed to the | |
/// <paramref name="importAction"/> callback. | |
/// </summary> | |
/// <typeparam name="TTargetType"> | |
/// The type to import. An object of this type will be passed to the | |
/// <paramref name="importAction"/> parameter callback. | |
/// </typeparam> | |
/// <typeparam name="TFactoryType"> | |
/// A type that exposes a method or property that can be invoked to obtain an instance | |
/// of <typeparamref name="TTargetType"/>. | |
/// </typeparam> | |
/// <param name="importAction"> | |
/// A callback function to invoke when an implementation of <typeparamref name="TTargetType"/> is created. | |
/// </param> | |
/// <param name="getTarget"> | |
/// A function used to obtain an instance of <typeparamref name="TTargetType"/> | |
/// by using an instance of <typeparamref name="TFactoryType"/>. | |
/// </param> | |
/// <param name="importName"> | |
/// The name of this import operation. If not null, exported classes without a matching name are excluded. | |
/// </param> | |
/// <param name="options"> | |
/// The import options to use. If null or not provided, the value returned by | |
/// <see cref="GetDefaultImportOptions"/> is returned. | |
/// </param> | |
protected void ImportSingle<TTargetType, TFactoryType>( | |
Action<TTargetType> importAction, | |
Func<TFactoryType, TTargetType> getTarget, | |
string importName = null, | |
ImportOptions options = null) | |
where TTargetType : class | |
where TFactoryType : class | |
{ | |
ImportSingleType( | |
importAction, | |
GetImportInfo<TTargetType>(importName, options, typeof(TFactoryType)), | |
t => CreateInstance(t, getTarget)); | |
} | |
/// <summary> | |
/// Imports the type specified by <typeparamref name="TTargetType"/>. When any class | |
/// with a public parameterless constructor is found that implements or inherits from | |
/// <typeparamref name="TTargetType"/>, then the one with the highest priority will be | |
/// created and passed to the <paramref name="importAction"/> parameter callback. | |
/// </summary> | |
/// <typeparam name="TTargetType"> | |
/// The type to import. An object of this type will be passed to the | |
/// <paramref name="importAction"/> parameter callback. | |
/// </typeparam> | |
/// <param name="importAction"> | |
/// A callback function to invoke when an implementation of <typeparamref name="TTargetType"/> is created. | |
/// </param> | |
/// <param name="importName"> | |
/// The name of this import operation. If not null, exported classes without a matching name are excluded. | |
/// </param> | |
/// <param name="options"> | |
/// The import options to use. If null or not provided, the value returned by | |
/// <see cref="GetDefaultImportOptions"/> is returned. | |
/// </param> | |
protected void ImportFirst<TTargetType>( | |
Action<TTargetType> importAction, | |
string importName = null, | |
ImportOptions options = null) | |
where TTargetType : class | |
{ | |
ImportFirstType( | |
importAction, | |
GetInstances<TTargetType>(importName, options)); | |
} | |
/// <summary> | |
/// Imports the type specified by <typeparamref name="TTargetType"/>. When any | |
/// class with a public parameterless constructor is found that implements or | |
/// inherits from either <typeparamref name="TTargetType"/> or | |
/// <typeparamref name="TFactoryType"/>, then an instance of the highest priority | |
/// class is created. If that instance is a <see cref="TTargetType"/>, than that | |
/// instance will be passed to the <paramref name="importAction"/> callback. If the | |
/// instance is a <typeparamref name="TFactoryType"/>, then an instance of | |
/// <typeparamref name="TTargetType"/> is obtained by using the | |
/// <paramref name="getTarget"/> function and passed to the | |
/// <paramref name="importAction"/> callback. | |
/// </summary> | |
/// <typeparam name="TTargetType"> | |
/// The type to import. An object of this type will be passed to the | |
/// <paramref name="importAction"/> parameter callback. | |
/// </typeparam> | |
/// <typeparam name="TFactoryType"> | |
/// A type that exposes a method or property that can be invoked to obtain an instance | |
/// of <typeparamref name="TTargetType"/>. | |
/// </typeparam> | |
/// <param name="importAction"> | |
/// A callback function to invoke when an implementation of <typeparamref name="TTargetType"/> is created. | |
/// </param> | |
/// <param name="getTarget"> | |
/// A function used to obtain an instance of <typeparamref name="TTargetType"/> | |
/// by using an instance of <typeparamref name="TFactoryType"/>. | |
/// </param> | |
/// <param name="importName"> | |
/// The name of this import operation. If not null, exported classes without a matching name are excluded. | |
/// </param> | |
/// <param name="options"> | |
/// The import options to use. If null or not provided, the value returned by | |
/// <see cref="GetDefaultImportOptions"/> is returned. | |
/// </param> | |
protected void ImportFirst<TTargetType, TFactoryType>( | |
Action<TTargetType> importAction, | |
Func<TFactoryType, TTargetType> getTarget, | |
string importName = null, | |
ImportOptions options = null) | |
where TTargetType : class | |
where TFactoryType : class | |
{ | |
ImportFirstType( | |
importAction, | |
GetInstances(getTarget, importName, options)); | |
} | |
/// <summary> | |
/// Imports the type specified by <typeparamref name="TTargetType"/> for many | |
/// implementations. When zero to many classes with a public parameterless | |
/// constructor are found that implements or inherits from | |
/// <typeparamref name="TTargetType"/>, then an instances of those class will be | |
/// created and passed to the <paramref name="importAction"/> parameter callback. | |
/// </summary> | |
/// <typeparam name="TTargetType"> | |
/// The type to import. Objects of this type will be passed to the | |
/// <paramref name="importAction"/> parameter callback. | |
/// </typeparam> | |
/// <param name="importAction"> | |
/// A callback function to invoke when a implementations of | |
/// <typeparamref name="TTargetType"/> are created. | |
/// </param> | |
/// <param name="importName"> | |
/// The name of this import operation. If not null, exported classes without a matching name are excluded. | |
/// </param> | |
/// <param name="options"> | |
/// The import options to use. If null or not provided, the value returned by | |
/// <see cref="GetDefaultImportOptions"/> is returned. | |
/// </param> | |
protected void ImportMultiple<TTargetType>( | |
Action<IEnumerable<TTargetType>> importAction, | |
string importName = null, | |
ImportOptions options = null) | |
where TTargetType : class | |
{ | |
importAction(GetInstances<TTargetType>(importName, options).ToList()); | |
} | |
/// <summary> | |
/// Imports the type specified by <typeparamref name="TTargetType"/> for many | |
/// implementations. When zero to many classes with a public parameterless | |
/// constructor are found that implements or inherits from either | |
/// <typeparamref name="TTargetType"/> or <typeparamref name="TFactoryType"/>, | |
/// then instances of those classes are created. If an instance is a | |
/// <see cref="TTargetType"/>, than that instance will be passed as part of a | |
/// collection to the <paramref name="importAction"/> callback. If an instance is a | |
/// <typeparamref name="TFactoryType"/>, then an instance of | |
/// <typeparamref name="TTargetType"/> is obtained by using the | |
/// <paramref name="getTarget"/> function and passed to the | |
/// <paramref name="importAction"/> callback. | |
/// </summary> | |
/// <typeparam name="TTargetType"> | |
/// The type to import. Objects of this type will be passed to the | |
/// <paramref name="importAction"/> parameter callback. | |
/// </typeparam> | |
/// <typeparam name="TFactoryType"> | |
/// A type that exposes a method or property that can be invoked to obtain an instance | |
/// of <typeparamref name="TTargetType"/>. | |
/// </typeparam> | |
/// <param name="importAction"> | |
/// A callback function to invoke when a implementations of | |
/// <typeparamref name="TTargetType"/> are created. | |
/// </param> | |
/// <param name="getTarget"> | |
/// A function used to obtain an instance of <typeparamref name="TTargetType"/> | |
/// by using an instance of <typeparamref name="TFactoryType"/>. | |
/// </param> | |
/// <param name="importName"> | |
/// The name of this import operation. If not null, exported classes without a matching name are excluded. | |
/// </param> | |
/// <param name="options"> | |
/// The import options to use. If null or not provided, the value returned by | |
/// <see cref="GetDefaultImportOptions"/> is returned. | |
/// </param> | |
protected void ImportMultiple<TTargetType, TFactoryType>( | |
Action<IEnumerable<TTargetType>> importAction, | |
Func<TFactoryType, TTargetType> getTarget, | |
string importName = null, | |
ImportOptions options = null) | |
where TTargetType : class | |
where TFactoryType : class | |
{ | |
importAction(GetInstances(getTarget, importName, options).ToList()); | |
} | |
private IEnumerable<TTargetType> GetInstances<TTargetType>(string importName, ImportOptions options) where TTargetType : class | |
{ | |
return GetInstances( | |
GetImportInfo<TTargetType>(importName, options), | |
CreateInstance<TTargetType>); | |
} | |
private IEnumerable<TTargetType> GetInstances<TTargetType, TFactoryType>(Func<TFactoryType, TTargetType> getTarget, string importName, ImportOptions options) | |
where TTargetType : class | |
where TFactoryType : class | |
{ | |
return GetInstances( | |
GetImportInfo<TTargetType>( | |
importName, | |
options, | |
typeof(TFactoryType)), | |
type => CreateInstance(type, getTarget)); | |
} | |
private ImportInfo GetImportInfo<TTargetType>( | |
string importName, | |
ImportOptions options, | |
Type factoryType = null) | |
where TTargetType : class | |
{ | |
return new ImportInfo( | |
importName, | |
typeof(TTargetType), | |
factoryType, | |
options ?? GetDefaultImportOptions()); | |
} | |
private void ImportSingleType<TTargetType>( | |
Action<TTargetType> importAction, | |
ImportInfo import, Func<Type, TTargetType> createInstance) | |
where TTargetType : class | |
{ | |
var candidateTypeNames = GetCandidateTypeNames(import); | |
var instance = | |
GetPrioritizedGroupsOfCandidateTypes(candidateTypeNames, import) | |
.Select(candidateTypes => ChooseCandidateType(candidateTypes, import)) | |
.Select(t => t == null ? null : createInstance(t)) | |
.FirstOrDefault(); | |
if (instance != null) | |
{ | |
importAction(instance); | |
} | |
} | |
private static void ImportFirstType<TTargetType>( | |
Action<TTargetType> importAction, | |
IEnumerable<TTargetType> instances) | |
where TTargetType : class | |
{ | |
var instance = instances.FirstOrDefault(); | |
if (instance != null) | |
{ | |
importAction(instance); | |
} | |
} | |
private IEnumerable<TTargetType> GetInstances<TTargetType>( | |
ImportInfo import, | |
Func<Type, TTargetType> createInstance) | |
where TTargetType : class | |
{ | |
var candidateTypeNames = GetCandidateTypeNames(import); | |
var prioritizedGroupsOfCandidateTypes = | |
GetPrioritizedGroupsOfCandidateTypes(candidateTypeNames, import); | |
return ( | |
from candidateTypes in prioritizedGroupsOfCandidateTypes | |
from candidateType in candidateTypes | |
select createInstance(candidateType)) | |
.Where(instance => instance != null); | |
} | |
private static TTargetType CreateInstance<TTargetType>(Type candidateType) | |
where TTargetType : class | |
{ | |
try | |
{ | |
var instance = Instantiate(candidateType); | |
var target = instance as TTargetType; | |
if (target != null) | |
{ | |
return target; | |
} | |
return null; | |
} | |
catch | |
{ | |
return null; | |
} | |
} | |
private static TTargetType CreateInstance<TTargetType, TFactoryType>( | |
Type candidateType, | |
Func<TFactoryType, TTargetType> getTarget) | |
where TTargetType : class | |
where TFactoryType : class | |
{ | |
try | |
{ | |
var instance = Instantiate(candidateType); | |
var factory = instance as TFactoryType; | |
if (factory != null) | |
{ | |
return getTarget(factory); | |
} | |
var target = instance as TTargetType; | |
if (target != null) | |
{ | |
return target; | |
} | |
return null; | |
} | |
catch | |
{ | |
return null; | |
} | |
} | |
private static object Instantiate(Type candidateType) | |
{ | |
if (candidateType.GetConstructor(Type.EmptyTypes) != null) | |
{ | |
return Activator.CreateInstance(candidateType); | |
} | |
var ctor = | |
candidateType.GetConstructors() | |
.OrderByDescending(c => c.GetParameters().Length) | |
.First(c => c.GetParameters().All(HasDefaultValue)); | |
var args = ctor.GetParameters().Select(p => p.DefaultValue).ToArray(); | |
return Activator.CreateInstance(candidateType, args); | |
} | |
private IEnumerable<IList<Type>> GetPrioritizedGroupsOfCandidateTypes( | |
IEnumerable<string> candidateTypeNames, | |
ImportInfo import) | |
{ | |
Func<Type, bool> isPreferredType; | |
if (import.FactoryType == null) | |
{ | |
isPreferredType = type => false; | |
} | |
else | |
{ | |
isPreferredType = GetIsTargetTypeFunc(import, import.FactoryType); | |
} | |
var prioritizedGroupsOfCandidateTypes = | |
candidateTypeNames.Select(GetExportInfo) | |
.Concat( | |
LoadExportInfosFromAssemblyAttributes( | |
import.TargetType, | |
import.Options.DirectoryPaths)) | |
.Where(export => | |
export != null | |
&& !export.Disabled | |
&& AreCompatible(import, export)) | |
.GroupBy(x => x.Priority) | |
.OrderByDescending(g => g.Key) | |
.Select(g => | |
g.OrderByDescending(export => isPreferredType(export.TargetClass)) | |
.ThenBy(export => export.TargetClass.AssemblyQualifiedName) | |
.ToList()) | |
.ToList(); | |
var uniqueExports = new List<ExportInfo>(); | |
var groupsToRemove = new List<List<ExportInfo>>(); | |
foreach (var group in prioritizedGroupsOfCandidateTypes) | |
{ | |
var exportsToRemove = new List<ExportInfo>(); | |
foreach (var export in group) | |
{ | |
if (uniqueExports.Any(uniqueExport => | |
export.TargetClass == uniqueExport.TargetClass | |
&& export.Name == uniqueExport.Name | |
&& export.Priority == uniqueExport.Priority)) | |
{ | |
exportsToRemove.Add(export); | |
} | |
else | |
{ | |
uniqueExports.Add(export); | |
} | |
} | |
foreach (var export in exportsToRemove) | |
{ | |
group.Remove(export); | |
} | |
if (group.Count == 0) | |
{ | |
groupsToRemove.Add(group); | |
} | |
} | |
foreach (var group in groupsToRemove) | |
{ | |
prioritizedGroupsOfCandidateTypes.Remove(group); | |
} | |
return | |
prioritizedGroupsOfCandidateTypes | |
.Select(group => group.Select(g => g.TargetClass).ToList()).ToList(); | |
} | |
private static bool AreCompatible(ImportInfo import, ExportInfo export) | |
{ | |
if (!import.Options.IncludeNamedExportsFromUnnamedImports | |
&& export.Name != null | |
&& import.Name == null) | |
{ | |
return false; | |
} | |
return (export.Name == import.Name || import.Name == null); | |
} | |
private IEnumerable<ExportInfo> LoadExportInfosFromAssemblyAttributes( | |
Type targetType, | |
IEnumerable<string> directoryPaths) | |
{ | |
try | |
{ | |
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += AppDomainOnReflectionOnlyAssemblyResolve; | |
return | |
GetAssemblyFiles(directoryPaths) | |
.SelectMany(assemblyFile => | |
LoadExportInfos(assemblyFile, targetType)) | |
.ToList(); | |
} | |
finally | |
{ | |
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= AppDomainOnReflectionOnlyAssemblyResolve; | |
} | |
} | |
private IEnumerable<ExportInfo> LoadExportInfos(string assemblyFile, Type targetType) | |
{ | |
try | |
{ | |
var assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile); | |
// Don't look here. | |
if (assembly.FullName == typeof(CompositionRootBase).Assembly.FullName) | |
{ | |
return Enumerable.Empty<ExportInfo>(); | |
} | |
return | |
GetExportInfos(CustomAttributeData.GetCustomAttributes(assembly)) | |
.Where(export => export.TargetClass.AssemblyQualifiedName == targetType.AssemblyQualifiedName); | |
} | |
catch | |
{ | |
return Enumerable.Empty<ExportInfo>(); | |
} | |
} | |
private static Type ChooseCandidateType( | |
IList<Type> candidateTypes, | |
ImportInfo import) | |
{ | |
if (candidateTypes.Count == 1) | |
{ | |
return candidateTypes[0]; | |
} | |
if (import.FactoryType != null) | |
{ | |
var data = | |
candidateTypes.Select(type => | |
{ | |
var interfaces = type.GetInterfaces(); | |
return new | |
{ | |
Type = type, | |
IsTarget = interfaces.Any(i => i.AssemblyQualifiedName == import.TargetTypeName), | |
IsFactory = interfaces.Any(i => i.AssemblyQualifiedName == import.FactoryTypeName) | |
}; | |
}).ToList(); | |
if (import.Options.PreferTTargetType) | |
{ | |
if ((data.Count(x => x.IsTarget) == 1) | |
&& (data.Count(x => x.IsFactory && !x.IsTarget) == (data.Count - 1))) | |
{ | |
return data.Single(x => x.IsTarget).Type; | |
} | |
} | |
else | |
{ | |
if ((data.Count(x => x.IsFactory) == 1) | |
&& (data.Count(x => x.IsTarget && !x.IsFactory) == (data.Count - 1))) | |
{ | |
return data.Single(x => x.IsFactory).Type; | |
} | |
} | |
} | |
return null; | |
} | |
private ExportInfo GetExportInfo(string assemblyQualifiedName) | |
{ | |
try | |
{ | |
var type = Type.GetType(assemblyQualifiedName); | |
if (type == null) | |
{ | |
return null; | |
} | |
return GetExportInfo(type); | |
} | |
catch | |
{ | |
return null; | |
} | |
} | |
private IEnumerable<string> GetCandidateTypeNames(ImportInfo import) | |
{ | |
var candidateTypeNamesCache = | |
_candidateTypeNamesByTargetTypeNameCache.GetOrAdd( | |
import.TargetTypeName, | |
_ => new ConcurrentDictionary<string, IEnumerable<string>>()); | |
var candidateTypeNames = | |
candidateTypeNamesCache.GetOrAdd( | |
import.TargetTypeName, | |
_ => | |
{ | |
var isTargetType = GetIsTargetTypeFunc(import, import.TargetType); | |
if (import.FactoryType != null) | |
{ | |
var isFactoryType = GetIsTargetTypeFunc(import, import.FactoryType); | |
var isTargetTypeLocal = isTargetType; | |
isTargetType = type => isTargetTypeLocal(type) || isFactoryType(type); | |
} | |
var candidateTypes = | |
_candidateTypesCache.GetOrAdd( | |
string.Join("|", import.Options.DirectoryPaths), | |
__ => GetCandidateTypes(import.Options.DirectoryPaths)); | |
return | |
candidateTypes | |
.Where(isTargetType) | |
.Select(t => t.AssemblyQualifiedName) | |
.ToList(); | |
}); | |
return candidateTypeNames; | |
} | |
private static Func<Type, bool> GetIsTargetTypeFunc(ImportInfo import, Type targetType) | |
{ | |
var targetTypeName = targetType.AssemblyQualifiedName; | |
if (targetTypeName == null) | |
{ | |
return typeInQuestion => false; | |
} | |
if (targetType.IsInterface) | |
{ | |
return typeInQuestion => | |
(typeInQuestion.IsPublic || import.Options.AllowNonPublicClasses) | |
&& typeInQuestion.GetInterfaces().Any(i => i.AssemblyQualifiedName == targetTypeName); | |
} | |
if (targetType.IsClass && !targetType.IsSealed) | |
{ | |
return typeInQuestion => | |
{ | |
var type = typeInQuestion; | |
while (type != null) | |
{ | |
if (type.AssemblyQualifiedName == targetTypeName) | |
{ | |
return true; | |
} | |
type = type.BaseType; | |
} | |
return false; | |
}; | |
} | |
return typeInQuestion => false; | |
} | |
private ICollection<Type> GetCandidateTypes(IEnumerable<string> directoryPaths) | |
{ | |
try | |
{ | |
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += AppDomainOnReflectionOnlyAssemblyResolve; | |
return GetAssemblyFiles(directoryPaths).SelectMany(LoadCandidateTypes).ToList(); | |
} | |
finally | |
{ | |
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= AppDomainOnReflectionOnlyAssemblyResolve; | |
} | |
} | |
private static Assembly AppDomainOnReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args) | |
{ | |
return Assembly.ReflectionOnlyLoad(args.Name); | |
} | |
private static IEnumerable<string> GetAssemblyFiles(IEnumerable<string> directoryPaths) | |
{ | |
foreach (var directoryPath in directoryPaths) | |
{ | |
IEnumerable<string> dllFiles; | |
try | |
{ | |
dllFiles = Directory.EnumerateFiles(directoryPath, "*.dll"); | |
} | |
catch | |
{ | |
dllFiles = Enumerable.Empty<string>(); | |
} | |
IEnumerable<string> exeFiles; | |
try | |
{ | |
exeFiles = Directory.EnumerateFiles(directoryPath, "*.exe"); | |
} | |
catch | |
{ | |
exeFiles = Enumerable.Empty<string>(); | |
} | |
foreach (var file in dllFiles.Concat(exeFiles)) | |
{ | |
yield return file; | |
} | |
} | |
} | |
private static IEnumerable<Type> LoadCandidateTypes(string assemblyFile) | |
{ | |
try | |
{ | |
var assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile); | |
// Don't look here. | |
if (assembly.FullName == typeof(CompositionRootBase).Assembly.FullName) | |
{ | |
return Enumerable.Empty<Type>(); | |
} | |
return | |
assembly.GetTypes() | |
.Where(t => | |
t.IsClass | |
&& !t.IsAbstract | |
&& t.AssemblyQualifiedName != null | |
&& HasDefaultishConstructor(t)); | |
} | |
catch | |
{ | |
return Enumerable.Empty<Type>(); | |
} | |
} | |
private static bool HasDefaultishConstructor(Type type) | |
{ | |
return | |
type.GetConstructor(Type.EmptyTypes) != null | |
|| type.GetConstructors().Any(ctor => ctor.GetParameters().All(HasDefaultValue)); | |
} | |
private static bool HasDefaultValue(ParameterInfo parameter) | |
{ | |
const ParameterAttributes hasDefaultValue = | |
ParameterAttributes.HasDefault | ParameterAttributes.Optional; | |
return (parameter.Attributes & hasDefaultValue) == hasDefaultValue; | |
} | |
private class ImportInfo | |
{ | |
private readonly string _name; | |
private readonly Type _targetType; | |
private readonly Type _factoryType; | |
private readonly ImportOptions _options; | |
public ImportInfo( | |
string name, | |
Type targetType, | |
Type factoryType, | |
ImportOptions options) | |
{ | |
_name = name; | |
_targetType = targetType; | |
_factoryType = factoryType; | |
_options = options; | |
} | |
internal string Name { get { return _name; } } | |
internal Type TargetType { get { return _targetType; } } | |
internal Type FactoryType { get { return _factoryType; } } | |
internal ImportOptions Options { get { return _options; } } | |
internal string TargetTypeName | |
{ | |
get | |
{ | |
return TargetType.AssemblyQualifiedName; | |
} | |
} | |
internal string FactoryTypeName | |
{ | |
get | |
{ | |
return | |
_factoryType == null | |
? null | |
: _factoryType.AssemblyQualifiedName; | |
} | |
} | |
} | |
} | |
internal class ExportInfo | |
{ | |
private readonly Type _targetClass; | |
private readonly int _priority; | |
internal ExportInfo(Type targetClass) | |
: this(targetClass, -1) | |
{ | |
} | |
internal ExportInfo(Type targetClass, int priority) | |
{ | |
if (targetClass.Assembly.ReflectionOnly) | |
{ | |
targetClass = Type.GetType(targetClass.AssemblyQualifiedName); | |
} | |
_targetClass = targetClass; | |
_priority = priority; | |
} | |
internal Type TargetClass { get { return _targetClass; } } | |
internal int Priority { get { return _priority; } } | |
internal string Name { get; set; } | |
internal bool Disabled { get; set; } | |
} | |
internal static class Extensions | |
{ | |
internal static IEnumerable<TAttribute> AsAttributeType<TAttribute>( | |
this IEnumerable<CustomAttributeData> attributeDataCollection) | |
where TAttribute : Attribute | |
{ | |
foreach (var attributeData in attributeDataCollection) | |
{ | |
TAttribute attribute; | |
if (attributeData.TryGetAttribute(out attribute)) | |
{ | |
yield return attribute; | |
} | |
} | |
} | |
private static bool TryGetAttribute<TAttribute>( | |
this CustomAttributeData attributeData, | |
out TAttribute attribute) | |
where TAttribute : Attribute | |
{ | |
var attributeType = typeof(TAttribute); | |
if (attributeData.Constructor == null | |
|| attributeData.Constructor.DeclaringType == null | |
|| attributeData.Constructor.DeclaringType.AssemblyQualifiedName == null | |
|| attributeData.Constructor.DeclaringType.AssemblyQualifiedName != attributeType.AssemblyQualifiedName) | |
{ | |
attribute = null; | |
return false; | |
} | |
var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; | |
var args = attributeData.ConstructorArguments.Select(x => x.Value).ToArray(); | |
attribute = (TAttribute)Activator.CreateInstance(attributeType, args); | |
if (attributeData.NamedArguments != null) | |
{ | |
foreach (var namedArgument in attributeData.NamedArguments) | |
{ | |
if (namedArgument.MemberInfo is PropertyInfo) | |
{ | |
var propertyInfo = attributeType.GetProperty(namedArgument.MemberInfo.Name, bindingFlags); | |
if (propertyInfo != null) | |
{ | |
propertyInfo.SetValue(attribute, namedArgument.TypedValue.Value, null); | |
} | |
} | |
else if (namedArgument.MemberInfo is FieldInfo) | |
{ | |
var fieldInfo = attributeType.GetField(namedArgument.MemberInfo.Name, bindingFlags); | |
if (fieldInfo != null) | |
{ | |
fieldInfo.SetValue(attribute, namedArgument.TypedValue.Value); | |
} | |
} | |
} | |
} | |
return true; | |
} | |
} | |
/// <summary> | |
/// Defines various options for an import operation. | |
/// </summary> | |
internal class ImportOptions | |
{ | |
private string[] _directoryPaths; | |
public ImportOptions() | |
{ | |
_directoryPaths = new[] { AppDomain.CurrentDomain.BaseDirectory }; | |
} | |
/// <summary> | |
/// Gets or sets a value indicating whether to allow non-public classes to be imported. | |
/// Default value is false. | |
/// </summary> | |
public bool AllowNonPublicClasses { get; set; } | |
/// <summary> | |
/// Gets or sets a value indicating whether a named export will be included from an | |
/// unnamed import operation. Default value is false. | |
/// </summary> | |
public bool IncludeNamedExportsFromUnnamedImports { get; set; } | |
/// <summary> | |
/// Gets or sets a value indicating whether, given equal priorities, an implementation | |
/// of TTargetType will be chosen over an implementation of TFactoryType. Default | |
/// value is false. | |
/// </summary> | |
public bool PreferTTargetType { get; set; } | |
/// <summary> | |
/// Gets or sets the directory paths that are searched for this import operation. The | |
/// default value is an array containing a single element: the value returned by | |
/// AppDomain.CurrentDomain.BaseDirectory. | |
/// </summary> | |
public IEnumerable<string> DirectoryPaths | |
{ | |
get { return _directoryPaths; } | |
set | |
{ | |
if (value != null) | |
{ | |
_directoryPaths = value.OrderBy(x => x).ToArray(); | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace PLACEHOLDER.StaticDependencyInjection | |
{ | |
/// <summary> | |
/// Indicates that a class should be exported as a static dependency. | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] | |
public sealed class ExportAttribute : Attribute | |
{ | |
private readonly int _priority; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ExportAttribute"/> class. | |
/// </summary> | |
public ExportAttribute() | |
: this(0) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ExportAttribute"/> class. | |
/// </summary> | |
/// <param name="priority">The priority of the class.</param> | |
public ExportAttribute(int priority) | |
{ | |
_priority = priority; | |
} | |
/// <summary> | |
/// Gets a value that indicates this class's relative priority. | |
/// </summary> | |
public int Priority { get { return _priority; } } | |
/// <summary> | |
/// Gets a value indicating whether this class is explicitly ineligible for exporting. | |
/// </summary> | |
public bool Disabled { get; set; } | |
/// <summary> | |
/// Gets or sets the arbitrary name of this export. Named import operations use | |
/// this value to filter eligible results. | |
/// </summary> | |
public string Name { get; set; } | |
} | |
/// <summary> | |
/// Indicates that a class should be exported as a static dependency. | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple=true)] | |
public class ExportExternalAttribute : Attribute | |
{ | |
private readonly Type _classType; | |
private readonly int _priority; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ExportExternalAttribute"/> class. | |
/// </summary> | |
/// <param name="classType">The type of the class to export.</param> | |
public ExportExternalAttribute(Type classType) | |
: this(classType, 0) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ExportExternalAttribute"/> class. | |
/// </summary> | |
/// <param name="classType">The type of the class to export.</param> | |
/// <param name="priority">The priority of the class.</param> | |
public ExportExternalAttribute(Type classType, int priority) | |
{ | |
_priority = priority; | |
_classType = classType; | |
} | |
/// <summary> | |
/// Gets the type of class to export. | |
/// </summary> | |
public Type ClassType { get { return _classType; } } | |
/// <summary> | |
/// Gets a value that indicates this class's relative priority. | |
/// </summary> | |
public int Priority { get { return _priority; } } | |
/// <summary> | |
/// Gets a value indicating whether this class is explicitly ineligible for exporting. | |
/// </summary> | |
public bool Disabled { get; set; } | |
/// <summary> | |
/// Gets or sets the arbitrary name of this export. Named import operations use | |
/// this value to filter eligible results. | |
/// </summary> | |
public string Name { get; set; } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////////////////////////////////////////////////////////////////////////////////////// | |
// // | |
// This file was generated by a tool. It would be a bad idea to make changes to it. // | |
// // | |
////////////////////////////////////////////////////////////////////////////////////// | |
namespace PLACEHOLDER.StaticDependencyInjection | |
{ | |
internal static class ModuleInitializer | |
{ | |
internal static void Run() | |
{ | |
new CompositionRoot().Bootstrap(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment