Init RaspberryIO

This commit is contained in:
2019-02-17 14:08:57 +01:00
commit e7c3f7af91
193 changed files with 36743 additions and 0 deletions
+204
View File
@@ -0,0 +1,204 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Runtime.InteropServices;
/// <summary>
/// A fixed-size buffer that acts as an infinite length one.
/// This buffer is backed by unmanaged, very fast memory so ensure you call
/// the dispose method when you are done using it.
/// Only for Windows.
/// </summary>
/// <seealso cref="System.IDisposable" />
public sealed class CircularBuffer : IDisposable
{
private readonly object _syncLock = new object();
private IntPtr _buffer = IntPtr.Zero;
/// <summary>
/// Initializes a new instance of the <see cref="CircularBuffer"/> class.
/// </summary>
/// <param name="bufferLength">Length of the buffer.</param>
public CircularBuffer(int bufferLength)
{
#if !NET452
if (Runtime.OS != Swan.OperatingSystem.Windows)
throw new InvalidOperationException("CircularBuffer component is only available in Windows");
#endif
Length = bufferLength;
_buffer = Marshal.AllocHGlobal(Length);
}
#region Properties
/// <summary>
/// Gets the capacity of this buffer.
/// </summary>
/// <value>
/// The length.
/// </value>
public int Length { get; private set; }
/// <summary>
/// Gets the current, 0-based read index.
/// </summary>
/// <value>
/// The index of the read.
/// </value>
public int ReadIndex { get; private set; }
/// <summary>
/// Gets the current, 0-based write index.
/// </summary>
/// <value>
/// The index of the write.
/// </value>
public int WriteIndex { get; private set; }
/// <summary>
/// Gets an the object associated with the last write.
/// </summary>
/// <value>
/// The write tag.
/// </value>
public TimeSpan WriteTag { get; private set; } = TimeSpan.MinValue;
/// <summary>
/// Gets the available bytes to read.
/// </summary>
/// <value>
/// The readable count.
/// </value>
public int ReadableCount { get; private set; }
/// <summary>
/// Gets the number of bytes that can be written.
/// </summary>
/// <value>
/// The writable count.
/// </value>
public int WritableCount => Length - ReadableCount;
/// <summary>
/// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0).
/// </summary>
/// <value>
/// The capacity percent.
/// </value>
public double CapacityPercent => 1.0 * ReadableCount / Length;
#endregion
#region Methods
/// <summary>
/// Reads the specified number of bytes into the target array.
/// </summary>
/// <param name="requestedBytes">The requested bytes.</param>
/// <param name="target">The target.</param>
/// <param name="targetOffset">The target offset.</param>
/// <exception cref="System.InvalidOperationException">
/// Exception that is thrown when a method call is invalid for the object's current state.
/// </exception>
public void Read(int requestedBytes, byte[] target, int targetOffset)
{
lock (_syncLock)
{
if (requestedBytes > ReadableCount)
{
throw new InvalidOperationException(
$"Unable to read {requestedBytes} bytes. Only {ReadableCount} bytes are available");
}
var readCount = 0;
while (readCount < requestedBytes)
{
var copyLength = Math.Min(Length - ReadIndex, requestedBytes - readCount);
var sourcePtr = _buffer + ReadIndex;
Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength);
readCount += copyLength;
ReadIndex += copyLength;
ReadableCount -= copyLength;
if (ReadIndex >= Length)
ReadIndex = 0;
}
}
}
/// <summary>
/// Writes data to the backing buffer using the specified pointer and length.
/// and associating a write tag for this operation.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="length">The length.</param>
/// <param name="writeTag">The write tag.</param>
/// <exception cref="System.InvalidOperationException">Unable to write to circular buffer. Call the Read method to make some additional room.</exception>
public void Write(IntPtr source, int length, TimeSpan writeTag)
{
lock (_syncLock)
{
if (ReadableCount + length > Length)
{
throw new InvalidOperationException(
$"Unable to write to circular buffer. Call the {nameof(Read)} method to make some additional room.");
}
var writeCount = 0;
while (writeCount < length)
{
var copyLength = Math.Min(Length - WriteIndex, length - writeCount);
var sourcePtr = source + writeCount;
var targetPtr = _buffer + WriteIndex;
CopyMemory(targetPtr, sourcePtr, (uint) copyLength);
writeCount += copyLength;
WriteIndex += copyLength;
ReadableCount += copyLength;
if (WriteIndex >= Length)
WriteIndex = 0;
}
WriteTag = writeTag;
}
}
/// <summary>
/// Resets all states as if this buffer had just been created.
/// </summary>
public void Clear()
{
lock (_syncLock)
{
WriteIndex = 0;
ReadIndex = 0;
WriteTag = TimeSpan.MinValue;
ReadableCount = 0;
}
}
/// <inheritdoc />
public void Dispose()
{
if (_buffer == IntPtr.Zero) return;
Marshal.FreeHGlobal(_buffer);
_buffer = IntPtr.Zero;
Length = 0;
}
/// <summary>
/// Fast pointer memory block copy function.
/// </summary>
/// <param name="destination">The destination.</param>
/// <param name="source">The source.</param>
/// <param name="length">The length.</param>
[DllImport("kernel32")]
public static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);
#endregion
}
}
+109
View File
@@ -0,0 +1,109 @@
namespace Unosquare.Swan.Components
{
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
/// <summary>
/// Represents a CsProjFile (and FsProjFile) parser.
/// </summary>
/// <remarks>
/// Based on https://github.com/maartenba/dotnetcli-init.
/// </remarks>
/// <typeparam name="T">The type of <c>CsProjMetadataBase</c>.</typeparam>
/// <seealso cref="System.IDisposable" />
public class CsProjFile<T>
: IDisposable
where T : CsProjMetadataBase
{
private readonly Stream _stream;
private readonly bool _leaveOpen;
private readonly XDocument _xmlDocument;
/// <summary>
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
/// </summary>
/// <param name="filename">The filename.</param>
public CsProjFile(string filename = null)
: this(OpenFile(filename))
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
/// <exception cref="ArgumentException">Project file is not of the new .csproj type.</exception>
public CsProjFile(Stream stream, bool leaveOpen = false)
{
_stream = stream;
_leaveOpen = leaveOpen;
_xmlDocument = XDocument.Load(stream);
var projectElement = _xmlDocument.Descendants("Project").FirstOrDefault();
var sdkAttribute = projectElement?.Attribute("Sdk");
var sdk = sdkAttribute?.Value;
if (sdk != "Microsoft.NET.Sdk" && sdk != "Microsoft.NET.Sdk.Web")
{
throw new ArgumentException("Project file is not of the new .csproj type.");
}
Metadata = Activator.CreateInstance<T>();
Metadata.SetData(_xmlDocument);
}
/// <summary>
/// Gets the metadata.
/// </summary>
/// <value>
/// The nu get metadata.
/// </value>
public T Metadata { get; }
/// <summary>
/// Saves this instance.
/// </summary>
public void Save()
{
_stream.SetLength(0);
_stream.Position = 0;
_xmlDocument.Save(_stream);
}
/// <inheritdoc />
public void Dispose()
{
if (!_leaveOpen)
{
_stream?.Dispose();
}
}
private static FileStream OpenFile(string filename)
{
if (filename == null)
{
filename = Directory
.EnumerateFiles(Directory.GetCurrentDirectory(), "*.csproj", SearchOption.TopDirectoryOnly)
.FirstOrDefault();
}
if (filename == null)
{
filename = Directory
.EnumerateFiles(Directory.GetCurrentDirectory(), "*.fsproj", SearchOption.TopDirectoryOnly)
.FirstOrDefault();
}
if (string.IsNullOrWhiteSpace(filename))
throw new ArgumentNullException(nameof(filename));
return File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
}
}
}
@@ -0,0 +1,73 @@
namespace Unosquare.Swan.Components
{
using System.Linq;
using System.Xml.Linq;
/// <summary>
/// Represents a CsProj metadata abstract class
/// to use with <c>CsProjFile</c> parser.
/// </summary>
public abstract class CsProjMetadataBase
{
private XDocument _xmlDocument;
/// <summary>
/// Gets the package identifier.
/// </summary>
/// <value>
/// The package identifier.
/// </value>
public string PackageId => FindElement(nameof(PackageId))?.Value;
/// <summary>
/// Gets the name of the assembly.
/// </summary>
/// <value>
/// The name of the assembly.
/// </value>
public string AssemblyName => FindElement(nameof(AssemblyName))?.Value;
/// <summary>
/// Gets the target frameworks.
/// </summary>
/// <value>
/// The target frameworks.
/// </value>
public string TargetFrameworks => FindElement(nameof(TargetFrameworks))?.Value;
/// <summary>
/// Gets the target framework.
/// </summary>
/// <value>
/// The target framework.
/// </value>
public string TargetFramework => FindElement(nameof(TargetFramework))?.Value;
/// <summary>
/// Gets the version.
/// </summary>
/// <value>
/// The version.
/// </value>
public string Version => FindElement(nameof(Version))?.Value;
/// <summary>
/// Parses the cs proj tags.
/// </summary>
/// <param name="args">The arguments.</param>
public abstract void ParseCsProjTags(ref string[] args);
/// <summary>
/// Sets the data.
/// </summary>
/// <param name="xmlDocument">The XML document.</param>
public void SetData(XDocument xmlDocument) => _xmlDocument = xmlDocument;
/// <summary>
/// Finds the element.
/// </summary>
/// <param name="elementName">Name of the element.</param>
/// <returns>A XElement.</returns>
protected XElement FindElement(string elementName) => _xmlDocument.Descendants(elementName).FirstOrDefault();
}
}
+144
View File
@@ -0,0 +1,144 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Abstractions;
/// <summary>
/// Represents logic providing several delay mechanisms.
/// </summary>
/// <example>
/// The following example shows how to implement delay mechanisms.
/// <code>
/// using Unosquare.Swan.Components;
///
/// public class Example
/// {
/// public static void Main()
/// {
/// // using the ThreadSleep strategy
/// using (var delay = new DelayProvider(DelayProvider.DelayStrategy.ThreadSleep))
/// {
/// // retrieve how much time was delayed
/// var time = delay.WaitOne();
/// }
/// }
/// }
/// </code>
/// </example>
public sealed class DelayProvider : IDisposable
{
private readonly object _syncRoot = new object();
private readonly Stopwatch _delayStopwatch = new Stopwatch();
private bool _isDisposed;
private IWaitEvent _delayEvent;
/// <summary>
/// Initializes a new instance of the <see cref="DelayProvider"/> class.
/// </summary>
/// <param name="strategy">The strategy.</param>
public DelayProvider(DelayStrategy strategy = DelayStrategy.TaskDelay)
{
Strategy = strategy;
}
/// <summary>
/// Enumerates the different ways of providing delays.
/// </summary>
public enum DelayStrategy
{
/// <summary>
/// Using the Thread.Sleep(15) mechanism.
/// </summary>
ThreadSleep,
/// <summary>
/// Using the Task.Delay(1).Wait mechanism.
/// </summary>
TaskDelay,
/// <summary>
/// Using a wait event that completes in a background ThreadPool thread.
/// </summary>
ThreadPool,
}
/// <summary>
/// Gets the selected delay strategy.
/// </summary>
public DelayStrategy Strategy { get; }
/// <summary>
/// Creates the smallest possible, synchronous delay based on the selected strategy.
/// </summary>
/// <returns>The elapsed time of the delay.</returns>
public TimeSpan WaitOne()
{
lock (_syncRoot)
{
if (_isDisposed) return TimeSpan.Zero;
_delayStopwatch.Restart();
switch (Strategy)
{
case DelayStrategy.ThreadSleep:
DelaySleep();
break;
case DelayStrategy.TaskDelay:
DelayTask();
break;
#if !NETSTANDARD1_3
case DelayStrategy.ThreadPool:
DelayThreadPool();
break;
#endif
}
return _delayStopwatch.Elapsed;
}
}
#region Dispose Pattern
/// <inheritdoc />
public void Dispose()
{
lock (_syncRoot)
{
if (_isDisposed) return;
_isDisposed = true;
_delayEvent?.Dispose();
}
}
#endregion
#region Private Delay Mechanisms
private static void DelaySleep() => Thread.Sleep(15);
private static void DelayTask() => Task.Delay(1).Wait();
#if !NETSTANDARD1_3
private void DelayThreadPool()
{
if (_delayEvent == null)
_delayEvent = WaitEventFactory.Create(isCompleted: true, useSlim: true);
_delayEvent.Begin();
ThreadPool.QueueUserWorkItem((s) =>
{
DelaySleep();
_delayEvent.Complete();
});
_delayEvent.Wait();
}
#endif
#endregion
}
}
@@ -0,0 +1,754 @@
namespace Unosquare.Swan.Components
{
using Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
/// <summary>
/// The concrete implementation of a simple IoC container
/// based largely on TinyIoC (https://github.com/grumpydev/TinyIoC).
/// </summary>
/// <seealso cref="System.IDisposable" />
public partial class DependencyContainer : IDisposable
{
private readonly object _autoRegisterLock = new object();
private bool _disposed;
static DependencyContainer()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DependencyContainer"/> class.
/// </summary>
public DependencyContainer()
{
RegisteredTypes = new TypesConcurrentDictionary(this);
Register(this);
// Only register the TinyMessenger singleton if we are the root container
if (Parent == null)
Register<IMessageHub, MessageHub>();
}
private DependencyContainer(DependencyContainer parent)
: this()
{
Parent = parent;
}
/// <summary>
/// Lazy created Singleton instance of the container for simple scenarios.
/// </summary>
public static DependencyContainer Current { get; } = new DependencyContainer();
internal DependencyContainer Parent { get; }
internal TypesConcurrentDictionary RegisteredTypes { get; }
/// <inheritdoc />
public void Dispose()
{
if (_disposed) return;
_disposed = true;
foreach (var disposable in RegisteredTypes.Values.Select(item => item as IDisposable))
{
disposable?.Dispose();
}
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the child container.
/// </summary>
/// <returns>A new instance of the <see cref="DependencyContainer"/> class.</returns>
public DependencyContainer GetChildContainer() => new DependencyContainer(this);
#region Registration
#if !NETSTANDARD1_3
/// <summary>
/// Attempt to automatically register all non-generic classes and interfaces in the current app domain.
/// Types will only be registered if they pass the supplied registration predicate.
/// </summary>
/// <param name="duplicateAction">What action to take when encountering duplicate implementations of an interface/base class.</param>
/// <param name="registrationPredicate">Predicate to determine if a particular type should be registered.</param>
public void AutoRegister(
DependencyContainerDuplicateImplementationActions duplicateAction =
DependencyContainerDuplicateImplementationActions.RegisterSingle,
Func<Type, bool> registrationPredicate = null)
{
AutoRegister(
Runtime.GetAssemblies().Where(a => !IsIgnoredAssembly(a)),
duplicateAction,
registrationPredicate);
}
#endif
/// <summary>
/// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies
/// Types will only be registered if they pass the supplied registration predicate.
/// </summary>
/// <param name="assemblies">Assemblies to process.</param>
/// <param name="duplicateAction">What action to take when encountering duplicate implementations of an interface/base class.</param>
/// <param name="registrationPredicate">Predicate to determine if a particular type should be registered.</param>
public void AutoRegister(
IEnumerable<Assembly> assemblies,
DependencyContainerDuplicateImplementationActions duplicateAction =
DependencyContainerDuplicateImplementationActions.RegisterSingle,
Func<Type, bool> registrationPredicate = null)
{
lock (_autoRegisterLock)
{
var types = assemblies
.SelectMany(a => a.GetAllTypes())
.Where(t => !IsIgnoredType(t, registrationPredicate))
.ToList();
var concreteTypes = types
.Where(type =>
type.IsClass() && !type.IsAbstract() &&
(type != GetType() && (type.DeclaringType != GetType()) && !type.IsGenericTypeDefinition()))
.ToList();
foreach (var type in concreteTypes)
{
try
{
RegisteredTypes.Register(type, string.Empty, GetDefaultObjectFactory(type, type));
}
catch (MethodAccessException)
{
// Ignore methods we can't access - added for Silverlight
}
}
var abstractInterfaceTypes = types.Where(
type =>
((type.IsInterface() || type.IsAbstract()) && (type.DeclaringType != GetType()) &&
(!type.IsGenericTypeDefinition())));
foreach (var type in abstractInterfaceTypes)
{
var localType = type;
var implementations = concreteTypes
.Where(implementationType => localType.IsAssignableFrom(implementationType)).ToList();
if (implementations.Skip(1).Any())
{
if (duplicateAction == DependencyContainerDuplicateImplementationActions.Fail)
throw new DependencyContainerRegistrationException(type, implementations);
if (duplicateAction == DependencyContainerDuplicateImplementationActions.RegisterMultiple)
{
RegisterMultiple(type, implementations);
}
}
var firstImplementation = implementations.FirstOrDefault();
if (firstImplementation == null) continue;
try
{
RegisteredTypes.Register(type, string.Empty, GetDefaultObjectFactory(type, firstImplementation));
}
catch (MethodAccessException)
{
// Ignore methods we can't access - added for Silverlight
}
}
}
}
/// <summary>
/// Creates/replaces a named container class registration with default options.
/// </summary>
/// <param name="registerType">Type to register.</param>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register(Type registerType, string name = "")
=> RegisteredTypes.Register(
registerType,
name,
GetDefaultObjectFactory(registerType, registerType));
/// <summary>
/// Creates/replaces a named container class registration with a given implementation and default options.
/// </summary>
/// <param name="registerType">Type to register.</param>
/// <param name="registerImplementation">Type to instantiate that implements RegisterType.</param>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register(Type registerType, Type registerImplementation, string name = "") =>
RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation));
/// <summary>
/// Creates/replaces a named container class registration with a specific, strong referenced, instance.
/// </summary>
/// <param name="registerType">Type to register.</param>
/// <param name="instance">Instance of RegisterType to register.</param>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register(Type registerType, object instance, string name = "") =>
RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerType, instance));
/// <summary>
/// Creates/replaces a named container class registration with a specific, strong referenced, instance.
/// </summary>
/// <param name="registerType">Type to register.</param>
/// <param name="registerImplementation">Type of instance to register that implements RegisterType.</param>
/// <param name="instance">Instance of RegisterImplementation to register.</param>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register(
Type registerType,
Type registerImplementation,
object instance,
string name = "")
=> RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerImplementation, instance));
/// <summary>
/// Creates/replaces a container class registration with a user specified factory.
/// </summary>
/// <param name="registerType">Type to register.</param>
/// <param name="factory">Factory/lambda that returns an instance of RegisterType.</param>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register(
Type registerType,
Func<DependencyContainer, Dictionary<string, object>, object> factory,
string name = "")
=> RegisteredTypes.Register(registerType, name, new DelegateFactory(registerType, factory));
/// <summary>
/// Creates/replaces a named container class registration with default options.
/// </summary>
/// <typeparam name="TRegister">Type to register.</typeparam>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister>(string name = "")
where TRegister : class
{
return Register(typeof(TRegister), name);
}
/// <summary>
/// Creates/replaces a named container class registration with a given implementation and default options.
/// </summary>
/// <typeparam name="TRegister">Type to register.</typeparam>
/// <typeparam name="TRegisterImplementation">Type to instantiate that implements RegisterType.</typeparam>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister, TRegisterImplementation>(string name = "")
where TRegister : class
where TRegisterImplementation : class, TRegister
{
return Register(typeof(TRegister), typeof(TRegisterImplementation), name);
}
/// <summary>
/// Creates/replaces a named container class registration with a specific, strong referenced, instance.
/// </summary>
/// <typeparam name="TRegister">Type to register.</typeparam>
/// <param name="instance">Instance of RegisterType to register.</param>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister>(TRegister instance, string name = "")
where TRegister : class
{
return Register(typeof(TRegister), instance, name);
}
/// <summary>
/// Creates/replaces a named container class registration with a specific, strong referenced, instance.
/// </summary>
/// <typeparam name="TRegister">Type to register.</typeparam>
/// <typeparam name="TRegisterImplementation">Type of instance to register that implements RegisterType.</typeparam>
/// <param name="instance">Instance of RegisterImplementation to register.</param>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister, TRegisterImplementation>(TRegisterImplementation instance,
string name = "")
where TRegister : class
where TRegisterImplementation : class, TRegister
{
return Register(typeof(TRegister), typeof(TRegisterImplementation), instance, name);
}
/// <summary>
/// Creates/replaces a named container class registration with a user specified factory.
/// </summary>
/// <typeparam name="TRegister">Type to register.</typeparam>
/// <param name="factory">Factory/lambda that returns an instance of RegisterType.</param>
/// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister>(
Func<DependencyContainer, Dictionary<string, object>, TRegister> factory, string name = "")
where TRegister : class
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
return Register(typeof(TRegister), factory, name);
}
/// <summary>
/// Register multiple implementations of a type.
///
/// Internally this registers each implementation using the full name of the class as its registration name.
/// </summary>
/// <typeparam name="TRegister">Type that each implementation implements.</typeparam>
/// <param name="implementationTypes">Types that implement RegisterType.</param>
/// <returns>MultiRegisterOptions for the fluent API.</returns>
public MultiRegisterOptions RegisterMultiple<TRegister>(IEnumerable<Type> implementationTypes) =>
RegisterMultiple(typeof(TRegister), implementationTypes);
/// <summary>
/// Register multiple implementations of a type.
///
/// Internally this registers each implementation using the full name of the class as its registration name.
/// </summary>
/// <param name="registrationType">Type that each implementation implements.</param>
/// <param name="implementationTypes">Types that implement RegisterType.</param>
/// <returns>MultiRegisterOptions for the fluent API.</returns>
public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable<Type> implementationTypes)
{
if (implementationTypes == null)
throw new ArgumentNullException(nameof(implementationTypes), "types is null.");
foreach (var type in implementationTypes.Where(type => !registrationType.IsAssignableFrom(type)))
{
throw new ArgumentException(
$"types: The type {registrationType.FullName} is not assignable from {type.FullName}");
}
if (implementationTypes.Count() != implementationTypes.Distinct().Count())
{
var queryForDuplicatedTypes = implementationTypes
.GroupBy(i => i)
.Where(j => j.Count() > 1)
.Select(j => j.Key.FullName);
var fullNamesOfDuplicatedTypes = string.Join(",\n", queryForDuplicatedTypes.ToArray());
throw new ArgumentException(
$"types: The same implementation type cannot be specified multiple times for {registrationType.FullName}\n\n{fullNamesOfDuplicatedTypes}");
}
var registerOptions = implementationTypes
.Select(type => Register(registrationType, type, type.FullName))
.ToList();
return new MultiRegisterOptions(registerOptions);
}
#endregion
#region Unregistration
/// <summary>
/// Remove a named container class registration.
/// </summary>
/// <typeparam name="TRegister">Type to unregister.</typeparam>
/// <param name="name">Name of registration.</param>
/// <returns><c>true</c> if the registration is successfully found and removed; otherwise, <c>false</c>.</returns>
public bool Unregister<TRegister>(string name = "") => Unregister(typeof(TRegister), name);
/// <summary>
/// Remove a named container class registration.
/// </summary>
/// <param name="registerType">Type to unregister.</param>
/// <param name="name">Name of registration.</param>
/// <returns><c>true</c> if the registration is successfully found and removed; otherwise, <c>false</c>.</returns>
public bool Unregister(Type registerType, string name = "") =>
RegisteredTypes.RemoveRegistration(new TypeRegistration(registerType, name));
#endregion
#region Resolution
/// <summary>
/// Attempts to resolve a named type using specified options and the supplied constructor parameters.
///
/// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
/// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
/// </summary>
/// <param name="resolveType">Type to resolve.</param>
/// <param name="name">Name of registration.</param>
/// <param name="options">Resolution options.</param>
/// <returns>Instance of type.</returns>
/// <exception cref="DependencyContainerResolutionException">Unable to resolve the type.</exception>
public object Resolve(
Type resolveType,
string name = null,
DependencyContainerResolveOptions options = null)
=> RegisteredTypes.ResolveInternal(new TypeRegistration(resolveType, name), options ?? DependencyContainerResolveOptions.Default);
/// <summary>
/// Attempts to resolve a named type using specified options and the supplied constructor parameters.
///
/// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
/// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
/// </summary>
/// <typeparam name="TResolveType">Type to resolve.</typeparam>
/// <param name="name">Name of registration.</param>
/// <param name="options">Resolution options.</param>
/// <returns>Instance of type.</returns>
/// <exception cref="DependencyContainerResolutionException">Unable to resolve the type.</exception>
public TResolveType Resolve<TResolveType>(
string name = null,
DependencyContainerResolveOptions options = null)
where TResolveType : class
{
return (TResolveType)Resolve(typeof(TResolveType), name, options);
}
/// <summary>
/// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options.
/// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
/// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
/// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
/// </summary>
/// <param name="resolveType">Type to resolve.</param>
/// <param name="name">The name.</param>
/// <param name="options">Resolution options.</param>
/// <returns>
/// Bool indicating whether the type can be resolved.
/// </returns>
public bool CanResolve(
Type resolveType,
string name = null,
DependencyContainerResolveOptions options = null) =>
RegisteredTypes.CanResolve(new TypeRegistration(resolveType, name), options);
/// <summary>
/// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options.
///
/// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
/// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
///
/// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
/// </summary>
/// <typeparam name="TResolveType">Type to resolve.</typeparam>
/// <param name="name">Name of registration.</param>
/// <param name="options">Resolution options.</param>
/// <returns>Bool indicating whether the type can be resolved.</returns>
public bool CanResolve<TResolveType>(
string name = null,
DependencyContainerResolveOptions options = null)
where TResolveType : class
{
return CanResolve(typeof(TResolveType), name, options);
}
/// <summary>
/// Attempts to resolve a type using the default options.
/// </summary>
/// <param name="resolveType">Type to resolve.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve(Type resolveType, out object resolvedType)
{
try
{
resolvedType = Resolve(resolveType);
return true;
}
catch (DependencyContainerResolutionException)
{
resolvedType = null;
return false;
}
}
/// <summary>
/// Attempts to resolve a type using the given options.
/// </summary>
/// <param name="resolveType">Type to resolve.</param>
/// <param name="options">Resolution options.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve(Type resolveType, DependencyContainerResolveOptions options, out object resolvedType)
{
try
{
resolvedType = Resolve(resolveType, options: options);
return true;
}
catch (DependencyContainerResolutionException)
{
resolvedType = null;
return false;
}
}
/// <summary>
/// Attempts to resolve a type using the default options and given name.
/// </summary>
/// <param name="resolveType">Type to resolve.</param>
/// <param name="name">Name of registration.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve(Type resolveType, string name, out object resolvedType)
{
try
{
resolvedType = Resolve(resolveType, name);
return true;
}
catch (DependencyContainerResolutionException)
{
resolvedType = null;
return false;
}
}
/// <summary>
/// Attempts to resolve a type using the given options and name.
/// </summary>
/// <param name="resolveType">Type to resolve.</param>
/// <param name="name">Name of registration.</param>
/// <param name="options">Resolution options.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve(
Type resolveType,
string name,
DependencyContainerResolveOptions options,
out object resolvedType)
{
try
{
resolvedType = Resolve(resolveType, name, options);
return true;
}
catch (DependencyContainerResolutionException)
{
resolvedType = null;
return false;
}
}
/// <summary>
/// Attempts to resolve a type using the default options.
/// </summary>
/// <typeparam name="TResolveType">Type to resolve.</typeparam>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve<TResolveType>(out TResolveType resolvedType)
where TResolveType : class
{
try
{
resolvedType = Resolve<TResolveType>();
return true;
}
catch (DependencyContainerResolutionException)
{
resolvedType = default;
return false;
}
}
/// <summary>
/// Attempts to resolve a type using the given options.
/// </summary>
/// <typeparam name="TResolveType">Type to resolve.</typeparam>
/// <param name="options">Resolution options.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve<TResolveType>(DependencyContainerResolveOptions options, out TResolveType resolvedType)
where TResolveType : class
{
try
{
resolvedType = Resolve<TResolveType>(options: options);
return true;
}
catch (DependencyContainerResolutionException)
{
resolvedType = default;
return false;
}
}
/// <summary>
/// Attempts to resolve a type using the default options and given name.
/// </summary>
/// <typeparam name="TResolveType">Type to resolve.</typeparam>
/// <param name="name">Name of registration.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve<TResolveType>(string name, out TResolveType resolvedType)
where TResolveType : class
{
try
{
resolvedType = Resolve<TResolveType>(name);
return true;
}
catch (DependencyContainerResolutionException)
{
resolvedType = default;
return false;
}
}
/// <summary>
/// Attempts to resolve a type using the given options and name.
/// </summary>
/// <typeparam name="TResolveType">Type to resolve.</typeparam>
/// <param name="name">Name of registration.</param>
/// <param name="options">Resolution options.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve<TResolveType>(
string name,
DependencyContainerResolveOptions options,
out TResolveType resolvedType)
where TResolveType : class
{
try
{
resolvedType = Resolve<TResolveType>(name, options);
return true;
}
catch (DependencyContainerResolutionException)
{
resolvedType = default;
return false;
}
}
/// <summary>
/// Returns all registrations of a type.
/// </summary>
/// <param name="resolveType">Type to resolveAll.</param>
/// <param name="includeUnnamed">Whether to include un-named (default) registrations.</param>
/// <returns>IEnumerable.</returns>
public IEnumerable<object> ResolveAll(Type resolveType, bool includeUnnamed = false)
=> RegisteredTypes.Resolve(resolveType, includeUnnamed);
/// <summary>
/// Returns all registrations of a type.
/// </summary>
/// <typeparam name="TResolveType">Type to resolveAll.</typeparam>
/// <param name="includeUnnamed">Whether to include un-named (default) registrations.</param>
/// <returns>IEnumerable.</returns>
public IEnumerable<TResolveType> ResolveAll<TResolveType>(bool includeUnnamed = true)
where TResolveType : class
{
return ResolveAll(typeof(TResolveType), includeUnnamed).Cast<TResolveType>();
}
/// <summary>
/// Attempts to resolve all public property dependencies on the given object using the given resolve options.
/// </summary>
/// <param name="input">Object to "build up".</param>
/// <param name="resolveOptions">Resolve options to use.</param>
public void BuildUp(object input, DependencyContainerResolveOptions resolveOptions = null)
{
if (resolveOptions == null)
resolveOptions = DependencyContainerResolveOptions.Default;
var properties = input.GetType()
.GetProperties()
.Where(property => property.GetCacheGetMethod() != null && property.GetCacheSetMethod() != null &&
!property.PropertyType.IsValueType());
foreach (var property in properties.Where(property => property.GetValue(input, null) == null))
{
try
{
property.SetValue(
input,
RegisteredTypes.ResolveInternal(new TypeRegistration(property.PropertyType), resolveOptions),
null);
}
catch (DependencyContainerResolutionException)
{
// Catch any resolution errors and ignore them
}
}
}
#endregion
#region Internal Methods
internal static bool IsValidAssignment(Type registerType, Type registerImplementation)
{
if (!registerType.IsGenericTypeDefinition())
{
if (!registerType.IsAssignableFrom(registerImplementation))
return false;
}
else
{
if (registerType.IsInterface() && registerImplementation.GetInterfaces().All(t => t.Name != registerType.Name))
return false;
if (registerType.IsAbstract() && registerImplementation.BaseType() != registerType)
return false;
}
return true;
}
#if !NETSTANDARD1_3
private static bool IsIgnoredAssembly(Assembly assembly)
{
// TODO - find a better way to remove "system" assemblies from the auto registration
var ignoreChecks = new List<Func<Assembly, bool>>
{
asm => asm.FullName.StartsWith("Microsoft.", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("System.", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("System,", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("CR_ExtUnitTest", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("mscorlib,", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("CR_VSTest", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("DevExpress.CodeRush", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("xunit.", StringComparison.Ordinal),
};
return ignoreChecks.Any(check => check(assembly));
}
#endif
private static bool IsIgnoredType(Type type, Func<Type, bool> registrationPredicate)
{
// TODO - find a better way to remove "system" types from the auto registration
var ignoreChecks = new List<Func<Type, bool>>()
{
t => t.FullName?.StartsWith("System.", StringComparison.Ordinal) ?? false,
t => t.FullName?.StartsWith("Microsoft.", StringComparison.Ordinal) ?? false,
t => t.IsPrimitive(),
t => t.IsGenericTypeDefinition(),
t => (t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0) &&
!(t.IsInterface() || t.IsAbstract()),
};
if (registrationPredicate != null)
{
ignoreChecks.Add(t => !registrationPredicate(t));
}
return ignoreChecks.Any(check => check(type));
}
private static ObjectFactoryBase GetDefaultObjectFactory(Type registerType, Type registerImplementation) => registerType.IsInterface() || registerType.IsAbstract()
? (ObjectFactoryBase)new SingletonFactory(registerType, registerImplementation)
: new MultiInstanceFactory(registerType, registerImplementation);
#endregion
}
}
@@ -0,0 +1,114 @@
namespace Unosquare.Swan.Components
{
using System.Collections.Generic;
/// <summary>
/// Resolution settings.
/// </summary>
public class DependencyContainerResolveOptions
{
/// <summary>
/// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found).
/// </summary>
public static DependencyContainerResolveOptions Default { get; } = new DependencyContainerResolveOptions();
/// <summary>
/// Gets or sets the unregistered resolution action.
/// </summary>
/// <value>
/// The unregistered resolution action.
/// </value>
public DependencyContainerUnregisteredResolutionActions UnregisteredResolutionAction { get; set; } =
DependencyContainerUnregisteredResolutionActions.AttemptResolve;
/// <summary>
/// Gets or sets the named resolution failure action.
/// </summary>
/// <value>
/// The named resolution failure action.
/// </value>
public DependencyContainerNamedResolutionFailureActions NamedResolutionFailureAction { get; set; } =
DependencyContainerNamedResolutionFailureActions.Fail;
/// <summary>
/// Gets the constructor parameters.
/// </summary>
/// <value>
/// The constructor parameters.
/// </value>
public Dictionary<string, object> ConstructorParameters { get; } = new Dictionary<string, object>();
/// <summary>
/// Clones this instance.
/// </summary>
/// <returns></returns>
public DependencyContainerResolveOptions Clone() => new DependencyContainerResolveOptions
{
NamedResolutionFailureAction = NamedResolutionFailureAction,
UnregisteredResolutionAction = UnregisteredResolutionAction,
};
}
/// <summary>
/// Defines Resolution actions.
/// </summary>
public enum DependencyContainerUnregisteredResolutionActions
{
/// <summary>
/// Attempt to resolve type, even if the type isn't registered.
///
/// Registered types/options will always take precedence.
/// </summary>
AttemptResolve,
/// <summary>
/// Fail resolution if type not explicitly registered
/// </summary>
Fail,
/// <summary>
/// Attempt to resolve unregistered type if requested type is generic
/// and no registration exists for the specific generic parameters used.
///
/// Registered types/options will always take precedence.
/// </summary>
GenericsOnly,
}
/// <summary>
/// Enumerates failure actions.
/// </summary>
public enum DependencyContainerNamedResolutionFailureActions
{
/// <summary>
/// The attempt unnamed resolution
/// </summary>
AttemptUnnamedResolution,
/// <summary>
/// The fail
/// </summary>
Fail,
}
/// <summary>
/// Enumerates duplicate definition actions.
/// </summary>
public enum DependencyContainerDuplicateImplementationActions
{
/// <summary>
/// The register single
/// </summary>
RegisterSingle,
/// <summary>
/// The register multiple
/// </summary>
RegisterMultiple,
/// <summary>
/// The fail
/// </summary>
Fail,
}
}
@@ -0,0 +1,13 @@
namespace Unosquare.Swan.Components
{
/// <summary>
/// A Message to be published/delivered by Messenger.
/// </summary>
public interface IMessageHubMessage
{
/// <summary>
/// The sender of the message, or null if not supported by the message implementation.
/// </summary>
object Sender { get; }
}
}
+466
View File
@@ -0,0 +1,466 @@
// ===============================================================================
// TinyIoC - TinyMessenger
//
// A simple messenger/event aggregator.
//
// https://github.com/grumpydev/TinyIoC/blob/master/src/TinyIoC/TinyMessenger.cs
// ===============================================================================
// Copyright © Steven Robbins. All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
// ===============================================================================
namespace Unosquare.Swan.Components
{
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Linq;
#region Message Types / Interfaces
/// <summary>
/// Represents a message subscription.
/// </summary>
public interface IMessageHubSubscription
{
/// <summary>
/// Token returned to the subscribed to reference this subscription.
/// </summary>
MessageHubSubscriptionToken SubscriptionToken { get; }
/// <summary>
/// Whether delivery should be attempted.
/// </summary>
/// <param name="message">Message that may potentially be delivered.</param>
/// <returns><c>true</c> - ok to send, <c>false</c> - should not attempt to send.</returns>
bool ShouldAttemptDelivery(IMessageHubMessage message);
/// <summary>
/// Deliver the message.
/// </summary>
/// <param name="message">Message to deliver.</param>
void Deliver(IMessageHubMessage message);
}
/// <summary>
/// Message proxy definition.
///
/// A message proxy can be used to intercept/alter messages and/or
/// marshal delivery actions onto a particular thread.
/// </summary>
public interface IMessageHubProxy
{
/// <summary>
/// Delivers the specified message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="subscription">The subscription.</param>
void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription);
}
/// <summary>
/// Default "pass through" proxy.
///
/// Does nothing other than deliver the message.
/// </summary>
public sealed class MessageHubDefaultProxy : IMessageHubProxy
{
private MessageHubDefaultProxy()
{
// placeholder
}
/// <summary>
/// Singleton instance of the proxy.
/// </summary>
public static MessageHubDefaultProxy Instance { get; } = new MessageHubDefaultProxy();
/// <summary>
/// Delivers the specified message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="subscription">The subscription.</param>
public void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription)
=> subscription.Deliver(message);
}
#endregion
#region Hub Interface
/// <summary>
/// Messenger hub responsible for taking subscriptions/publications and delivering of messages.
/// </summary>
public interface IMessageHub
{
/// <summary>
/// Subscribe to a message type with the given destination and delivery action.
/// Messages will be delivered via the specified proxy.
///
/// All messages of this type will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>MessageSubscription used to unsubscribing.</returns>
MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
bool useStrongReferences,
IMessageHubProxy proxy)
where TMessage : class, IMessageHubMessage;
/// <summary>
/// Subscribe to a message type with the given destination and delivery action with the given filter.
/// Messages will be delivered via the specified proxy.
/// All references are held with WeakReferences
/// Only messages that "pass" the filter will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="messageFilter">The message filter.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>
/// MessageSubscription used to unsubscribing.
/// </returns>
MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter,
bool useStrongReferences,
IMessageHubProxy proxy)
where TMessage : class, IMessageHubMessage;
/// <summary>
/// Unsubscribe from a particular message type.
///
/// Does not throw an exception if the subscription is not found.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="subscriptionToken">Subscription token received from Subscribe.</param>
void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
where TMessage : class, IMessageHubMessage;
/// <summary>
/// Publish a message to any subscribers.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="message">Message to deliver.</param>
void Publish<TMessage>(TMessage message)
where TMessage : class, IMessageHubMessage;
/// <summary>
/// Publish a message to any subscribers asynchronously.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="message">Message to deliver.</param>
/// <returns>A task from Publish action.</returns>
Task PublishAsync<TMessage>(TMessage message)
where TMessage : class, IMessageHubMessage;
}
#endregion
#region Hub Implementation
/// <inheritdoc />
/// <example>
/// The following code describes how to use a MessageHub. Both the
/// subscription and the message sending are done in the same place but this is only for explanatory purposes.
/// <code>
/// class Example
/// {
/// using Unosquare.Swan;
/// using Unosquare.Swan.Components;
///
/// static void Main()
/// {
/// // using DependencyContainer to create an instance of MessageHub
/// var messageHub = DependencyContainer
/// .Current
/// .Resolve&lt;IMessageHub&gt;() as MessageHub;
///
/// // create an instance of the publisher class
/// // which has a string as its content
/// var message = new MessageHubGenericMessage&lt;string&gt;(new object(), "SWAN");
///
/// // subscribe to the publisher's event
/// // and just print out the content which is a string
/// // a token is returned which can be used to unsubscribe later on
/// var token = messageHub
/// .Subscribe&lt;MessageHubGenericMessage&lt;string&gt;&gt;(m =&gt; m.Content.Info());
///
/// // publish the message described above which is
/// // the string 'SWAN'
/// messageHub.Publish(message);
///
/// // unsuscribe, we will no longer receive any messages
/// messageHub.Unsubscribe&lt;MessageHubGenericMessage&lt;string&gt;&gt;(token);
///
/// Terminal.Flush();
/// }
///
/// }
/// </code>
/// </example>
public sealed class MessageHub : IMessageHub
{
#region Private Types and Interfaces
private readonly object _subscriptionsPadlock = new object();
private readonly Dictionary<Type, List<SubscriptionItem>> _subscriptions =
new Dictionary<Type, List<SubscriptionItem>>();
private class WeakMessageSubscription<TMessage> : IMessageHubSubscription
where TMessage : class, IMessageHubMessage
{
private readonly WeakReference _deliveryAction;
private readonly WeakReference _messageFilter;
/// <summary>
/// Initializes a new instance of the <see cref="WeakMessageSubscription{TMessage}" /> class.
/// </summary>
/// <param name="subscriptionToken">The subscription token.</param>
/// <param name="deliveryAction">The delivery action.</param>
/// <param name="messageFilter">The message filter.</param>
/// <exception cref="ArgumentNullException">subscriptionToken
/// or
/// deliveryAction
/// or
/// messageFilter.</exception>
public WeakMessageSubscription(
MessageHubSubscriptionToken subscriptionToken,
Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter)
{
SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
_deliveryAction = new WeakReference(deliveryAction);
_messageFilter = new WeakReference(messageFilter);
}
public MessageHubSubscriptionToken SubscriptionToken { get; }
public bool ShouldAttemptDelivery(IMessageHubMessage message)
{
return _deliveryAction.IsAlive && _messageFilter.IsAlive &&
((Func<TMessage, bool>) _messageFilter.Target).Invoke((TMessage) message);
}
public void Deliver(IMessageHubMessage message)
{
if (_deliveryAction.IsAlive)
{
((Action<TMessage>) _deliveryAction.Target).Invoke((TMessage) message);
}
}
}
private class StrongMessageSubscription<TMessage> : IMessageHubSubscription
where TMessage : class, IMessageHubMessage
{
private readonly Action<TMessage> _deliveryAction;
private readonly Func<TMessage, bool> _messageFilter;
/// <summary>
/// Initializes a new instance of the <see cref="StrongMessageSubscription{TMessage}" /> class.
/// </summary>
/// <param name="subscriptionToken">The subscription token.</param>
/// <param name="deliveryAction">The delivery action.</param>
/// <param name="messageFilter">The message filter.</param>
/// <exception cref="ArgumentNullException">subscriptionToken
/// or
/// deliveryAction
/// or
/// messageFilter.</exception>
public StrongMessageSubscription(
MessageHubSubscriptionToken subscriptionToken,
Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter)
{
SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
_deliveryAction = deliveryAction;
_messageFilter = messageFilter;
}
public MessageHubSubscriptionToken SubscriptionToken { get; }
public bool ShouldAttemptDelivery(IMessageHubMessage message) => _messageFilter.Invoke((TMessage) message);
public void Deliver(IMessageHubMessage message) => _deliveryAction.Invoke((TMessage) message);
}
#endregion
#region Subscription dictionary
private class SubscriptionItem
{
public SubscriptionItem(IMessageHubProxy proxy, IMessageHubSubscription subscription)
{
Proxy = proxy;
Subscription = subscription;
}
public IMessageHubProxy Proxy { get; }
public IMessageHubSubscription Subscription { get; }
}
#endregion
#region Public API
/// <summary>
/// Subscribe to a message type with the given destination and delivery action.
/// Messages will be delivered via the specified proxy.
///
/// All messages of this type will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction. </param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>MessageSubscription used to unsubscribing.</returns>
public MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
bool useStrongReferences = true,
IMessageHubProxy proxy = null)
where TMessage : class, IMessageHubMessage
{
return Subscribe(deliveryAction, m => true, useStrongReferences, proxy);
}
/// <summary>
/// Subscribe to a message type with the given destination and delivery action with the given filter.
/// Messages will be delivered via the specified proxy.
/// All references are held with WeakReferences
/// Only messages that "pass" the filter will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="messageFilter">The message filter.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>
/// MessageSubscription used to unsubscribing.
/// </returns>
public MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter,
bool useStrongReferences = true,
IMessageHubProxy proxy = null)
where TMessage : class, IMessageHubMessage
{
if (deliveryAction == null)
throw new ArgumentNullException(nameof(deliveryAction));
if (messageFilter == null)
throw new ArgumentNullException(nameof(messageFilter));
lock (_subscriptionsPadlock)
{
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
{
currentSubscriptions = new List<SubscriptionItem>();
_subscriptions[typeof(TMessage)] = currentSubscriptions;
}
var subscriptionToken = new MessageHubSubscriptionToken(this, typeof(TMessage));
IMessageHubSubscription subscription;
if (useStrongReferences)
{
subscription = new StrongMessageSubscription<TMessage>(
subscriptionToken,
deliveryAction,
messageFilter);
}
else
{
subscription = new WeakMessageSubscription<TMessage>(
subscriptionToken,
deliveryAction,
messageFilter);
}
currentSubscriptions.Add(new SubscriptionItem(proxy ?? MessageHubDefaultProxy.Instance, subscription));
return subscriptionToken;
}
}
/// <inheritdoc />
public void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
where TMessage : class, IMessageHubMessage
{
if (subscriptionToken == null)
throw new ArgumentNullException(nameof(subscriptionToken));
lock (_subscriptionsPadlock)
{
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
return;
var currentlySubscribed = currentSubscriptions
.Where(sub => ReferenceEquals(sub.Subscription.SubscriptionToken, subscriptionToken))
.ToList();
currentlySubscribed.ForEach(sub => currentSubscriptions.Remove(sub));
}
}
/// <summary>
/// Publish a message to any subscribers.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="message">Message to deliver.</param>
public void Publish<TMessage>(TMessage message)
where TMessage : class, IMessageHubMessage
{
if (message == null)
throw new ArgumentNullException(nameof(message));
List<SubscriptionItem> currentlySubscribed;
lock (_subscriptionsPadlock)
{
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
return;
currentlySubscribed = currentSubscriptions
.Where(sub => sub.Subscription.ShouldAttemptDelivery(message))
.ToList();
}
currentlySubscribed.ForEach(sub =>
{
try
{
sub.Proxy.Deliver(message, sub.Subscription);
}
catch
{
// Ignore any errors and carry on
}
});
}
/// <summary>
/// Publish a message to any subscribers asynchronously.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="message">Message to deliver.</param>
/// <returns>A task with the publish.</returns>
public Task PublishAsync<TMessage>(TMessage message)
where TMessage : class, IMessageHubMessage
{
return Task.Run(() => Publish(message));
}
#endregion
}
#endregion
}
@@ -0,0 +1,59 @@
namespace Unosquare.Swan.Components
{
using System;
/// <summary>
/// Base class for messages that provides weak reference storage of the sender.
/// </summary>
public abstract class MessageHubMessageBase
: IMessageHubMessage
{
/// <summary>
/// Store a WeakReference to the sender just in case anyone is daft enough to
/// keep the message around and prevent the sender from being collected.
/// </summary>
private readonly WeakReference _sender;
/// <summary>
/// Initializes a new instance of the <see cref="MessageHubMessageBase"/> class.
/// </summary>
/// <param name="sender">The sender.</param>
/// <exception cref="System.ArgumentNullException">sender.</exception>
protected MessageHubMessageBase(object sender)
{
if (sender == null)
throw new ArgumentNullException(nameof(sender));
_sender = new WeakReference(sender);
}
/// <summary>
/// The sender of the message, or null if not supported by the message implementation.
/// </summary>
public object Sender => _sender?.Target;
}
/// <summary>
/// Generic message with user specified content.
/// </summary>
/// <typeparam name="TContent">Content type to store.</typeparam>
public class MessageHubGenericMessage<TContent>
: MessageHubMessageBase
{
/// <summary>
/// Initializes a new instance of the <see cref="MessageHubGenericMessage{TContent}"/> class.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="content">The content.</param>
public MessageHubGenericMessage(object sender, TContent content)
: base(sender)
{
Content = content;
}
/// <summary>
/// Contents of the message.
/// </summary>
public TContent Content { get; protected set; }
}
}
@@ -0,0 +1,54 @@
namespace Unosquare.Swan.Components
{
using System;
#if NETSTANDARD1_3
using System.Reflection;
#endif
/// <summary>
/// Represents an active subscription to a message.
/// </summary>
public sealed class MessageHubSubscriptionToken
: IDisposable
{
private readonly WeakReference _hub;
private readonly Type _messageType;
/// <summary>
/// Initializes a new instance of the <see cref="MessageHubSubscriptionToken"/> class.
/// </summary>
/// <param name="hub">The hub.</param>
/// <param name="messageType">Type of the message.</param>
/// <exception cref="System.ArgumentNullException">hub.</exception>
/// <exception cref="System.ArgumentOutOfRangeException">messageType.</exception>
public MessageHubSubscriptionToken(IMessageHub hub, Type messageType)
{
if (hub == null)
{
throw new ArgumentNullException(nameof(hub));
}
if (!typeof(IMessageHubMessage).IsAssignableFrom(messageType))
{
throw new ArgumentOutOfRangeException(nameof(messageType));
}
_hub = new WeakReference(hub);
_messageType = messageType;
}
/// <inheritdoc />
public void Dispose()
{
if (_hub.IsAlive && _hub.Target is IMessageHub hub)
{
var unsubscribeMethod = typeof(IMessageHub).GetMethod(nameof(IMessageHub.Unsubscribe),
new[] {typeof(MessageHubSubscriptionToken)});
unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(_messageType);
unsubscribeMethod.Invoke(hub, new object[] {this});
}
GC.SuppressFinalize(this);
}
}
}
@@ -0,0 +1,424 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections.Generic;
using System.Reflection;
using Exceptions;
/// <summary>
/// Represents an abstract class for Object Factory.
/// </summary>
public abstract class ObjectFactoryBase
{
/// <summary>
/// Whether to assume this factory successfully constructs its objects
///
/// Generally set to true for delegate style factories as CanResolve cannot delve
/// into the delegates they contain.
/// </summary>
public virtual bool AssumeConstruction => false;
/// <summary>
/// The type the factory instantiates.
/// </summary>
public abstract Type CreatesType { get; }
/// <summary>
/// Constructor to use, if specified.
/// </summary>
public ConstructorInfo Constructor { get; private set; }
/// <summary>
/// Gets the singleton variant.
/// </summary>
/// <value>
/// The singleton variant.
/// </value>
/// <exception cref="DependencyContainerRegistrationException">singleton.</exception>
public virtual ObjectFactoryBase SingletonVariant =>
throw new DependencyContainerRegistrationException(GetType(), "singleton");
/// <summary>
/// Gets the multi instance variant.
/// </summary>
/// <value>
/// The multi instance variant.
/// </value>
/// <exception cref="DependencyContainerRegistrationException">multi-instance.</exception>
public virtual ObjectFactoryBase MultiInstanceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "multi-instance");
/// <summary>
/// Gets the strong reference variant.
/// </summary>
/// <value>
/// The strong reference variant.
/// </value>
/// <exception cref="DependencyContainerRegistrationException">strong reference.</exception>
public virtual ObjectFactoryBase StrongReferenceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "strong reference");
/// <summary>
/// Gets the weak reference variant.
/// </summary>
/// <value>
/// The weak reference variant.
/// </value>
/// <exception cref="DependencyContainerRegistrationException">weak reference.</exception>
public virtual ObjectFactoryBase WeakReferenceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "weak reference");
/// <summary>
/// Create the type.
/// </summary>
/// <param name="requestedType">Type user requested to be resolved.</param>
/// <param name="container">Container that requested the creation.</param>
/// <param name="options">The options.</param>
/// <returns> Instance of type. </returns>
public abstract object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options);
/// <summary>
/// Gets the factory for child container.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="parent">The parent.</param>
/// <param name="child">The child.</param>
/// <returns></returns>
public virtual ObjectFactoryBase GetFactoryForChildContainer(
Type type,
DependencyContainer parent,
DependencyContainer child)
{
return this;
}
}
/// <inheritdoc />
/// <summary>
/// IObjectFactory that creates new instances of types for each resolution.
/// </summary>
internal class MultiInstanceFactory : ObjectFactoryBase
{
private readonly Type _registerType;
private readonly Type _registerImplementation;
public MultiInstanceFactory(Type registerType, Type registerImplementation)
{
if (registerImplementation.IsAbstract() || registerImplementation.IsInterface())
{
throw new DependencyContainerRegistrationException(registerImplementation,
"MultiInstanceFactory",
true);
}
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
{
throw new DependencyContainerRegistrationException(registerImplementation,
"MultiInstanceFactory",
true);
}
_registerType = registerType;
_registerImplementation = registerImplementation;
}
public override Type CreatesType => _registerImplementation;
public override ObjectFactoryBase SingletonVariant =>
new SingletonFactory(_registerType, _registerImplementation);
public override ObjectFactoryBase MultiInstanceVariant => this;
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
try
{
return container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options);
}
catch (DependencyContainerResolutionException ex)
{
throw new DependencyContainerResolutionException(_registerType, ex);
}
}
}
/// <inheritdoc />
/// <summary>
/// IObjectFactory that invokes a specified delegate to construct the object.
/// </summary>
internal class DelegateFactory : ObjectFactoryBase
{
private readonly Type _registerType;
private readonly Func<DependencyContainer, Dictionary<string, object>, object> _factory;
public DelegateFactory(
Type registerType,
Func<DependencyContainer,
Dictionary<string, object>, object> factory)
{
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
_registerType = registerType;
}
public override bool AssumeConstruction => true;
public override Type CreatesType => _registerType;
public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(_registerType, _factory);
public override ObjectFactoryBase StrongReferenceVariant => this;
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
try
{
return _factory.Invoke(container, options.ConstructorParameters);
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(_registerType, ex);
}
}
}
/// <inheritdoc />
/// <summary>
/// IObjectFactory that invokes a specified delegate to construct the object
/// Holds the delegate using a weak reference.
/// </summary>
internal class WeakDelegateFactory : ObjectFactoryBase
{
private readonly Type _registerType;
private readonly WeakReference _factory;
public WeakDelegateFactory(Type registerType,
Func<DependencyContainer, Dictionary<string, object>, object> factory)
{
if (factory == null)
throw new ArgumentNullException(nameof(factory));
_factory = new WeakReference(factory);
_registerType = registerType;
}
public override bool AssumeConstruction => true;
public override Type CreatesType => _registerType;
public override ObjectFactoryBase StrongReferenceVariant
{
get
{
if (!(_factory.Target is Func<DependencyContainer, Dictionary<string, object>, object> factory))
throw new DependencyContainerWeakReferenceException(_registerType);
return new DelegateFactory(_registerType, factory);
}
}
public override ObjectFactoryBase WeakReferenceVariant => this;
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
if (!(_factory.Target is Func<DependencyContainer, Dictionary<string, object>, object> factory))
throw new DependencyContainerWeakReferenceException(_registerType);
try
{
return factory.Invoke(container, options.ConstructorParameters);
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(_registerType, ex);
}
}
}
/// <summary>
/// Stores an particular instance to return for a type.
/// </summary>
internal class InstanceFactory : ObjectFactoryBase, IDisposable
{
private readonly Type _registerType;
private readonly Type _registerImplementation;
private readonly object _instance;
public InstanceFactory(Type registerType, Type registerImplementation, object instance)
{
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true);
_registerType = registerType;
_registerImplementation = registerImplementation;
_instance = instance;
}
public override bool AssumeConstruction => true;
public override Type CreatesType => _registerImplementation;
public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(_registerType, _registerImplementation);
public override ObjectFactoryBase WeakReferenceVariant =>
new WeakInstanceFactory(_registerType, _registerImplementation, _instance);
public override ObjectFactoryBase StrongReferenceVariant => this;
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
return _instance;
}
public void Dispose()
{
var disposable = _instance as IDisposable;
disposable?.Dispose();
}
}
/// <summary>
/// Stores the instance with a weak reference.
/// </summary>
internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable
{
private readonly Type _registerType;
private readonly Type _registerImplementation;
private readonly WeakReference _instance;
public WeakInstanceFactory(Type registerType, Type registerImplementation, object instance)
{
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
{
throw new DependencyContainerRegistrationException(
registerImplementation,
"WeakInstanceFactory",
true);
}
_registerType = registerType;
_registerImplementation = registerImplementation;
_instance = new WeakReference(instance);
}
public override Type CreatesType => _registerImplementation;
public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(_registerType, _registerImplementation);
public override ObjectFactoryBase WeakReferenceVariant => this;
public override ObjectFactoryBase StrongReferenceVariant
{
get
{
var instance = _instance.Target;
if (instance == null)
throw new DependencyContainerWeakReferenceException(_registerType);
return new InstanceFactory(_registerType, _registerImplementation, instance);
}
}
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
var instance = _instance.Target;
if (instance == null)
throw new DependencyContainerWeakReferenceException(_registerType);
return instance;
}
public void Dispose() => (_instance.Target as IDisposable)?.Dispose();
}
/// <summary>
/// A factory that lazy instantiates a type and always returns the same instance.
/// </summary>
internal class SingletonFactory : ObjectFactoryBase, IDisposable
{
private readonly Type _registerType;
private readonly Type _registerImplementation;
private readonly object _singletonLock = new object();
private object _current;
public SingletonFactory(Type registerType, Type registerImplementation)
{
if (registerImplementation.IsAbstract() || registerImplementation.IsInterface())
{
throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
}
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
{
throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
}
_registerType = registerType;
_registerImplementation = registerImplementation;
}
public override Type CreatesType => _registerImplementation;
public override ObjectFactoryBase SingletonVariant => this;
public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(_registerType, _registerImplementation);
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
if (options.ConstructorParameters.Count != 0)
throw new ArgumentException("Cannot specify parameters for singleton types");
lock (_singletonLock)
{
if (_current == null)
_current = container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options);
}
return _current;
}
public override ObjectFactoryBase GetFactoryForChildContainer(
Type type,
DependencyContainer parent,
DependencyContainer child)
{
// We make sure that the singleton is constructed before the child container takes the factory.
// Otherwise the results would vary depending on whether or not the parent container had resolved
// the type before the child container does.
GetObject(type, parent, DependencyContainerResolveOptions.Default);
return this;
}
public void Dispose() => (_current as IDisposable)?.Dispose();
}
}
@@ -0,0 +1,46 @@
namespace Unosquare.Swan.Components
{
/// <summary>
/// Represents the text of the standard output and standard error
/// of a process, including its exit code.
/// </summary>
public class ProcessResult
{
/// <summary>
/// Initializes a new instance of the <see cref="ProcessResult" /> class.
/// </summary>
/// <param name="exitCode">The exit code.</param>
/// <param name="standardOutput">The standard output.</param>
/// <param name="standardError">The standard error.</param>
public ProcessResult(int exitCode, string standardOutput, string standardError)
{
ExitCode = exitCode;
StandardOutput = standardOutput;
StandardError = standardError;
}
/// <summary>
/// Gets the exit code.
/// </summary>
/// <value>
/// The exit code.
/// </value>
public int ExitCode { get; }
/// <summary>
/// Gets the text of the standard output.
/// </summary>
/// <value>
/// The standard output.
/// </value>
public string StandardOutput { get; }
/// <summary>
/// Gets the text of the standard error.
/// </summary>
/// <value>
/// The standard error.
/// </value>
public string StandardError { get; }
}
}
+474
View File
@@ -0,0 +1,474 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Provides methods to help create external processes, and efficiently capture the
/// standard error and standard output streams.
/// </summary>
public static class ProcessRunner
{
/// <summary>
/// Defines a delegate to handle binary data reception from the standard
/// output or standard error streams from a process.
/// </summary>
/// <param name="processData">The process data.</param>
/// <param name="process">The process.</param>
public delegate void ProcessDataReceivedCallback(byte[] processData, Process process);
/// <summary>
/// Runs the process asynchronously and if the exit code is 0,
/// returns all of the standard output text. If the exit code is something other than 0
/// it returns the contents of standard error.
/// This method is meant to be used for programs that output a relatively small amount of text.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>The type of the result produced by this Task.</returns>
public static Task<string> GetProcessOutputAsync(string filename, CancellationToken ct = default) =>
GetProcessOutputAsync(filename, string.Empty, ct);
/// <summary>
/// Runs the process asynchronously and if the exit code is 0,
/// returns all of the standard output text. If the exit code is something other than 0
/// it returns the contents of standard error.
/// This method is meant to be used for programs that output a relatively small amount of text.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>The type of the result produced by this Task.</returns>
/// <example>
/// The following code explains how to run an external process using the
/// <see cref="GetProcessOutputAsync(string, string, CancellationToken)"/> method.
/// <code>
/// class Example
/// {
/// using System.Threading.Tasks;
/// using Unosquare.Swan.Components;
///
/// static async Task Main()
/// {
/// // execute a process and save its output
/// var data = await ProcessRunner.
/// GetProcessOutputAsync("dotnet", "--help");
///
/// // print the output
/// data.WriteLine();
/// }
/// }
/// </code>
/// </example>
public static async Task<string> GetProcessOutputAsync(
string filename,
string arguments,
CancellationToken ct = default)
{
var result = await GetProcessResultAsync(filename, arguments, ct).ConfigureAwait(false);
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
}
/// <summary>
/// Gets the process output asynchronous.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="workingDirectory">The working directory.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// The type of the result produced by this Task.
/// </returns>
public static async Task<string> GetProcessOutputAsync(
string filename,
string arguments,
string workingDirectory,
CancellationToken ct = default)
{
var result = await GetProcessResultAsync(filename, arguments, workingDirectory, ct: ct).ConfigureAwait(false);
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
}
/// <summary>
/// Runs the process asynchronously and if the exit code is 0,
/// returns all of the standard output text. If the exit code is something other than 0
/// it returns the contents of standard error.
/// This method is meant to be used for programs that output a relatively small amount
/// of text using a different encoder.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="encoding">The encoding.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// The type of the result produced by this Task.
/// </returns>
public static async Task<string> GetProcessEncodedOutputAsync(
string filename,
string arguments = "",
Encoding encoding = null,
CancellationToken ct = default)
{
var result = await GetProcessResultAsync(filename, arguments, null, encoding, ct).ConfigureAwait(false);
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
}
/// <summary>
/// Executes a process asynchronously and returns the text of the standard output and standard error streams
/// along with the exit code. This method is meant to be used for programs that output a relatively small
/// amount of text.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance.
/// </returns>
/// <exception cref="ArgumentNullException">filename.</exception>
public static Task<ProcessResult> GetProcessResultAsync(
string filename,
string arguments = "",
CancellationToken ct = default) =>
GetProcessResultAsync(filename, arguments, null, Definitions.CurrentAnsiEncoding, ct);
/// <summary>
/// Executes a process asynchronously and returns the text of the standard output and standard error streams
/// along with the exit code. This method is meant to be used for programs that output a relatively small
/// amount of text.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="workingDirectory">The working directory.</param>
/// <param name="encoding">The encoding.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance.
/// </returns>
/// <exception cref="ArgumentNullException">filename.</exception>
/// <example>
/// The following code describes how to run an external process using the <see cref="GetProcessResultAsync(string, string, string, Encoding, CancellationToken)" /> method.
/// <code>
/// class Example
/// {
/// using System.Threading.Tasks;
/// using Unosquare.Swan.Components;
/// static async Task Main()
/// {
/// // Execute a process asynchronously
/// var data = await ProcessRunner.GetProcessResultAsync("dotnet", "--help");
/// // print out the exit code
/// $"{data.ExitCode}".WriteLine();
/// // print out the output
/// data.StandardOutput.WriteLine();
/// // and the error if exists
/// data.StandardError.Error();
/// }
/// }
/// </code></example>
public static async Task<ProcessResult> GetProcessResultAsync(
string filename,
string arguments,
string workingDirectory,
Encoding encoding = null,
CancellationToken ct = default)
{
if (filename == null)
throw new ArgumentNullException(nameof(filename));
if (encoding == null)
encoding = Definitions.CurrentAnsiEncoding;
var standardOutputBuilder = new StringBuilder();
var standardErrorBuilder = new StringBuilder();
var processReturn = await RunProcessAsync(
filename,
arguments,
workingDirectory,
(data, proc) => { standardOutputBuilder.Append(encoding.GetString(data)); },
(data, proc) => { standardErrorBuilder.Append(encoding.GetString(data)); },
encoding,
true,
ct)
.ConfigureAwait(false);
return new ProcessResult(processReturn, standardOutputBuilder.ToString(), standardErrorBuilder.ToString());
}
/// <summary>
/// Runs an external process asynchronously, providing callbacks to
/// capture binary data from the standard error and standard output streams.
/// The callbacks contain a reference to the process so you can respond to output or
/// error streams by writing to the process' input stream.
/// The exit code (return value) will be -1 for forceful termination of the process.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="workingDirectory">The working directory.</param>
/// <param name="onOutputData">The on output data.</param>
/// <param name="onErrorData">The on error data.</param>
/// <param name="encoding">The encoding.</param>
/// <param name="syncEvents">if set to <c>true</c> the next data callback will wait until the current one completes.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Value type will be -1 for forceful termination of the process.
/// </returns>
public static Task<int> RunProcessAsync(
string filename,
string arguments,
string workingDirectory,
ProcessDataReceivedCallback onOutputData,
ProcessDataReceivedCallback onErrorData,
Encoding encoding,
bool syncEvents = true,
CancellationToken ct = default)
{
if (filename == null)
throw new ArgumentNullException(nameof(filename));
return Task.Run(() =>
{
// Setup the process and its corresponding start info
var process = new Process
{
EnableRaisingEvents = false,
StartInfo = new ProcessStartInfo
{
Arguments = arguments,
CreateNoWindow = true,
FileName = filename,
RedirectStandardError = true,
StandardErrorEncoding = encoding,
RedirectStandardOutput = true,
StandardOutputEncoding = encoding,
UseShellExecute = false,
#if NET452
WindowStyle = ProcessWindowStyle.Hidden,
#endif
},
};
if (!string.IsNullOrWhiteSpace(workingDirectory))
process.StartInfo.WorkingDirectory = workingDirectory;
// Launch the process and discard any buffered data for standard error and standard output
process.Start();
process.StandardError.DiscardBufferedData();
process.StandardOutput.DiscardBufferedData();
// Launch the asynchronous stream reading tasks
var readTasks = new Task[2];
readTasks[0] = CopyStreamAsync(
process,
process.StandardOutput.BaseStream,
onOutputData,
syncEvents,
ct);
readTasks[1] = CopyStreamAsync(
process,
process.StandardError.BaseStream,
onErrorData,
syncEvents,
ct);
try
{
// Wait for all tasks to complete
Task.WaitAll(readTasks, ct);
}
catch (TaskCanceledException)
{
// ignore
}
finally
{
// Wait for the process to exit
while (ct.IsCancellationRequested == false)
{
if (process.HasExited || process.WaitForExit(5))
break;
}
// Forcefully kill the process if it do not exit
try
{
if (process.HasExited == false)
process.Kill();
}
catch
{
// swallow
}
}
try
{
// Retrieve and return the exit code.
// -1 signals error
return process.HasExited ? process.ExitCode : -1;
}
catch
{
return -1;
}
}, ct);
}
/// <summary>
/// Runs an external process asynchronously, providing callbacks to
/// capture binary data from the standard error and standard output streams.
/// The callbacks contain a reference to the process so you can respond to output or
/// error streams by writing to the process' input stream.
/// The exit code (return value) will be -1 for forceful termination of the process.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="onOutputData">The on output data.</param>
/// <param name="onErrorData">The on error data.</param>
/// <param name="syncEvents">if set to <c>true</c> the next data callback will wait until the current one completes.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>Value type will be -1 for forceful termination of the process.</returns>
/// <example>
/// The following example illustrates how to run an external process using the
/// <see cref="RunProcessAsync(string, string, ProcessDataReceivedCallback, ProcessDataReceivedCallback, bool, CancellationToken)"/>
/// method.
/// <code>
/// class Example
/// {
/// using System.Diagnostics;
/// using System.Text;
/// using System.Threading.Tasks;
/// using Unosquare.Swan;
/// using Unosquare.Swan.Components;
///
/// static async Task Main()
/// {
/// // Execute a process asynchronously
/// var data = await ProcessRunner
/// .RunProcessAsync("dotnet", "--help", Print, Print);
///
/// // flush all messages
/// Terminal.Flush();
/// }
///
/// // a callback to print both output or errors
/// static void Print(byte[] data, Process proc) =>
/// Encoding.GetEncoding(0).GetString(data).WriteLine();
/// }
/// </code>
/// </example>
public static Task<int> RunProcessAsync(
string filename,
string arguments,
ProcessDataReceivedCallback onOutputData,
ProcessDataReceivedCallback onErrorData,
bool syncEvents = true,
CancellationToken ct = default)
=> RunProcessAsync(
filename,
arguments,
null,
onOutputData,
onErrorData,
Definitions.CurrentAnsiEncoding,
syncEvents,
ct);
/// <summary>
/// Copies the stream asynchronously.
/// </summary>
/// <param name="process">The process.</param>
/// <param name="baseStream">The source stream.</param>
/// <param name="onDataCallback">The on data callback.</param>
/// <param name="syncEvents">if set to <c>true</c> [synchronize events].</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>Total copies stream.</returns>
private static Task<ulong> CopyStreamAsync(
Process process,
Stream baseStream,
ProcessDataReceivedCallback onDataCallback,
bool syncEvents,
CancellationToken ct)
{
return Task.Factory.StartNew(async () =>
{
// define some state variables
var swapBuffer = new byte[2048]; // the buffer to copy data from one stream to the next
ulong totalCount = 0; // the total amount of bytes read
var hasExited = false;
while (ct.IsCancellationRequested == false)
{
try
{
// Check if process is no longer valid
// if this condition holds, simply read the last bits of data available.
int readCount; // the bytes read in any given event
if (process.HasExited || process.WaitForExit(1))
{
while (true)
{
try
{
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
if (readCount > 0)
{
totalCount += (ulong) readCount;
onDataCallback?.Invoke(swapBuffer.Skip(0).Take(readCount).ToArray(), process);
}
else
{
hasExited = true;
break;
}
}
catch
{
hasExited = true;
break;
}
}
}
if (hasExited) break;
// Try reading from the stream. < 0 means no read occurred.
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
// When no read is done, we need to let is rest for a bit
if (readCount <= 0)
{
await Task.Delay(1, ct); // do not hog CPU cycles doing nothing.
continue;
}
totalCount += (ulong) readCount;
if (onDataCallback == null) continue;
// Create the buffer to pass to the callback
var eventBuffer = swapBuffer.Skip(0).Take(readCount).ToArray();
// Create the data processing callback invocation
var eventTask =
Task.Factory.StartNew(() => { onDataCallback.Invoke(eventBuffer, process); }, ct);
// wait for the event to process before the next read occurs
if (syncEvents) eventTask.Wait(ct);
}
catch
{
break;
}
}
return totalCount;
}, ct).Unwrap();
}
}
}
+143
View File
@@ -0,0 +1,143 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Diagnostics;
using Abstractions;
/// <summary>
/// A time measurement artifact.
/// </summary>
internal sealed class RealTimeClock : IDisposable
{
private readonly Stopwatch _chrono = new Stopwatch();
private ISyncLocker _locker = SyncLockerFactory.Create(useSlim: true);
private long _offsetTicks;
private double _speedRatio = 1.0d;
private bool _isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="RealTimeClock"/> class.
/// The clock starts paused and at the 0 position.
/// </summary>
public RealTimeClock()
{
Reset();
}
/// <summary>
/// Gets or sets the clock position.
/// </summary>
public TimeSpan Position
{
get
{
using (_locker.AcquireReaderLock())
{
return TimeSpan.FromTicks(
_offsetTicks + Convert.ToInt64(_chrono.Elapsed.Ticks * SpeedRatio));
}
}
}
/// <summary>
/// Gets a value indicating whether the clock is running.
/// </summary>
public bool IsRunning
{
get
{
using (_locker.AcquireReaderLock())
{
return _chrono.IsRunning;
}
}
}
/// <summary>
/// Gets or sets the speed ratio at which the clock runs.
/// </summary>
public double SpeedRatio
{
get
{
using (_locker.AcquireReaderLock())
{
return _speedRatio;
}
}
set
{
using (_locker.AcquireWriterLock())
{
if (value < 0d) value = 0d;
// Capture the initial position se we set it even after the speedratio has changed
// this ensures a smooth position transition
var initialPosition = Position;
_speedRatio = value;
Update(initialPosition);
}
}
}
/// <summary>
/// Sets a new position value atomically.
/// </summary>
/// <param name="value">The new value that the position porperty will hold.</param>
public void Update(TimeSpan value)
{
using (_locker.AcquireWriterLock())
{
var resume = _chrono.IsRunning;
_chrono.Reset();
_offsetTicks = value.Ticks;
if (resume) _chrono.Start();
}
}
/// <summary>
/// Starts or resumes the clock.
/// </summary>
public void Play()
{
using (_locker.AcquireWriterLock())
{
if (_chrono.IsRunning) return;
_chrono.Start();
}
}
/// <summary>
/// Pauses the clock.
/// </summary>
public void Pause()
{
using (_locker.AcquireWriterLock())
{
_chrono.Stop();
}
}
/// <summary>
/// Sets the clock position to 0 and stops it.
/// The speed ratio is not modified.
/// </summary>
public void Reset()
{
using (_locker.AcquireWriterLock())
{
_offsetTicks = 0;
_chrono.Reset();
}
}
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
_locker?.Dispose();
_locker = null;
}
}
}
@@ -0,0 +1,132 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections.Generic;
using System.Linq;
using Exceptions;
/// <summary>
/// Registration options for "fluent" API.
/// </summary>
public sealed class RegisterOptions
{
private readonly TypesConcurrentDictionary _registeredTypes;
private readonly DependencyContainer.TypeRegistration _registration;
/// <summary>
/// Initializes a new instance of the <see cref="RegisterOptions" /> class.
/// </summary>
/// <param name="registeredTypes">The registered types.</param>
/// <param name="registration">The registration.</param>
public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration)
{
_registeredTypes = registeredTypes;
_registration = registration;
}
/// <summary>
/// Make registration a singleton (single instance) if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions AsSingleton()
{
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null)
throw new DependencyContainerRegistrationException(_registration.Type, "singleton");
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.SingletonVariant);
}
/// <summary>
/// Make registration multi-instance if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions AsMultiInstance()
{
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null)
throw new DependencyContainerRegistrationException(_registration.Type, "multi-instance");
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.MultiInstanceVariant);
}
/// <summary>
/// Make registration hold a weak reference if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions WithWeakReference()
{
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null)
throw new DependencyContainerRegistrationException(_registration.Type, "weak reference");
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.WeakReferenceVariant);
}
/// <summary>
/// Make registration hold a strong reference if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions WithStrongReference()
{
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null)
throw new DependencyContainerRegistrationException(_registration.Type, "strong reference");
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.StrongReferenceVariant);
}
}
/// <summary>
/// Registration options for "fluent" API when registering multiple implementations.
/// </summary>
public sealed class MultiRegisterOptions
{
private IEnumerable<RegisterOptions> _registerOptions;
/// <summary>
/// Initializes a new instance of the <see cref="MultiRegisterOptions"/> class.
/// </summary>
/// <param name="registerOptions">The register options.</param>
public MultiRegisterOptions(IEnumerable<RegisterOptions> registerOptions)
{
_registerOptions = registerOptions;
}
/// <summary>
/// Make registration a singleton (single instance) if possible.
/// </summary>
/// <returns>A registration multi-instance for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception>
public MultiRegisterOptions AsSingleton()
{
_registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsSingleton());
return this;
}
/// <summary>
/// Make registration multi-instance if possible.
/// </summary>
/// <returns>A registration multi-instance for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception>
public MultiRegisterOptions AsMultiInstance()
{
_registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance());
return this;
}
private IEnumerable<RegisterOptions> ExecuteOnAllRegisterOptions(
Func<RegisterOptions, RegisterOptions> action)
{
return _registerOptions.Select(action).ToList();
}
}
}
@@ -0,0 +1,67 @@
namespace Unosquare.Swan.Components
{
using System;
public partial class DependencyContainer
{
/// <summary>
/// Represents a Type Registration within the IoC Container.
/// </summary>
public sealed class TypeRegistration
{
private readonly int _hashCode;
/// <summary>
/// Initializes a new instance of the <see cref="TypeRegistration"/> class.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="name">The name.</param>
public TypeRegistration(Type type, string name = null)
{
Type = type;
Name = name ?? string.Empty;
_hashCode = string.Concat(Type.FullName, "|", Name).GetHashCode();
}
/// <summary>
/// Gets the type.
/// </summary>
/// <value>
/// The type.
/// </value>
public Type Type { get; }
/// <summary>
/// Gets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; }
/// <summary>
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{
if (!(obj is TypeRegistration typeRegistration) || typeRegistration.Type != Type)
return false;
return string.Compare(Name, typeRegistration.Name, StringComparison.Ordinal) == 0;
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode() => _hashCode;
}
}
}
@@ -0,0 +1,352 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using Exceptions;
using System.Collections.Concurrent;
/// <summary>
/// Represents a Concurrent Dictionary for TypeRegistration.
/// </summary>
public class TypesConcurrentDictionary : ConcurrentDictionary<DependencyContainer.TypeRegistration, ObjectFactoryBase>
{
private static readonly ConcurrentDictionary<ConstructorInfo, ObjectConstructor> ObjectConstructorCache =
new ConcurrentDictionary<ConstructorInfo, ObjectConstructor>();
private readonly DependencyContainer _dependencyContainer;
internal TypesConcurrentDictionary(DependencyContainer dependencyContainer)
{
_dependencyContainer = dependencyContainer;
}
/// <summary>
/// Represents a delegate to build an object with the parameters.
/// </summary>
/// <param name="parameters">The parameters.</param>
/// <returns>The built object.</returns>
public delegate object ObjectConstructor(params object[] parameters);
internal IEnumerable<object> Resolve(Type resolveType, bool includeUnnamed)
{
var registrations = Keys.Where(tr => tr.Type == resolveType)
.Concat(GetParentRegistrationsForType(resolveType)).Distinct();
if (!includeUnnamed)
registrations = registrations.Where(tr => tr.Name != string.Empty);
return registrations.Select(registration =>
ResolveInternal(registration, DependencyContainerResolveOptions.Default));
}
internal ObjectFactoryBase GetCurrentFactory(DependencyContainer.TypeRegistration registration)
{
TryGetValue(registration, out var current);
return current;
}
internal RegisterOptions Register(Type registerType, string name, ObjectFactoryBase factory)
=> AddUpdateRegistration(new DependencyContainer.TypeRegistration(registerType, name), factory);
internal RegisterOptions AddUpdateRegistration(DependencyContainer.TypeRegistration typeRegistration, ObjectFactoryBase factory)
{
this[typeRegistration] = factory;
return new RegisterOptions(this, typeRegistration);
}
internal bool RemoveRegistration(DependencyContainer.TypeRegistration typeRegistration)
=> TryRemove(typeRegistration, out _);
internal object ResolveInternal(
DependencyContainer.TypeRegistration registration,
DependencyContainerResolveOptions options = null)
{
if (options == null)
options = DependencyContainerResolveOptions.Default;
// Attempt container resolution
if (TryGetValue(registration, out var factory))
{
try
{
return factory.GetObject(registration.Type, _dependencyContainer, options);
}
catch (DependencyContainerResolutionException)
{
throw;
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(registration.Type, ex);
}
}
// Attempt to get a factory from parent if we can
var bubbledObjectFactory = GetParentObjectFactory(registration);
if (bubbledObjectFactory != null)
{
try
{
return bubbledObjectFactory.GetObject(registration.Type, _dependencyContainer, options);
}
catch (DependencyContainerResolutionException)
{
throw;
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(registration.Type, ex);
}
}
// Fail if requesting named resolution and settings set to fail if unresolved
if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
DependencyContainerNamedResolutionFailureActions.Fail)
throw new DependencyContainerResolutionException(registration.Type);
// Attempted unnamed fallback container resolution if relevant and requested
if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution)
{
if (TryGetValue(new DependencyContainer.TypeRegistration(registration.Type, string.Empty), out factory))
{
try
{
return factory.GetObject(registration.Type, _dependencyContainer, options);
}
catch (DependencyContainerResolutionException)
{
throw;
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(registration.Type, ex);
}
}
}
// Attempt unregistered construction if possible and requested
var isValid = (options.UnregisteredResolutionAction ==
DependencyContainerUnregisteredResolutionActions.AttemptResolve) ||
(registration.Type.IsGenericType() && options.UnregisteredResolutionAction ==
DependencyContainerUnregisteredResolutionActions.GenericsOnly);
return isValid && !registration.Type.IsAbstract() && !registration.Type.IsInterface()
? ConstructType(registration.Type, null, options)
: throw new DependencyContainerResolutionException(registration.Type);
}
internal bool CanResolve(
DependencyContainer.TypeRegistration registration,
DependencyContainerResolveOptions options = null)
{
if (options == null)
options = DependencyContainerResolveOptions.Default;
var checkType = registration.Type;
var name = registration.Name;
if (TryGetValue(new DependencyContainer.TypeRegistration(checkType, name), out var factory))
{
if (factory.AssumeConstruction)
return true;
if (factory.Constructor == null)
return GetBestConstructor(factory.CreatesType, options) != null;
return CanConstruct(factory.Constructor, options);
}
// Fail if requesting named resolution and settings set to fail if unresolved
// Or bubble up if we have a parent
if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
DependencyContainerNamedResolutionFailureActions.Fail)
return _dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false;
// Attempted unnamed fallback container resolution if relevant and requested
if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution)
{
if (TryGetValue(new DependencyContainer.TypeRegistration(checkType), out factory))
{
if (factory.AssumeConstruction)
return true;
return GetBestConstructor(factory.CreatesType, options) != null;
}
}
// Check if type is an automatic lazy factory request or an IEnumerable<ResolveType>
if (IsAutomaticLazyFactoryRequest(checkType) || registration.Type.IsIEnumerable())
return true;
// Attempt unregistered construction if possible and requested
// If we cant', bubble if we have a parent
if ((options.UnregisteredResolutionAction ==
DependencyContainerUnregisteredResolutionActions.AttemptResolve) ||
(checkType.IsGenericType() && options.UnregisteredResolutionAction ==
DependencyContainerUnregisteredResolutionActions.GenericsOnly))
{
return (GetBestConstructor(checkType, options) != null) ||
(_dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false);
}
// Bubble resolution up the container tree if we have a parent
return _dependencyContainer.Parent != null && _dependencyContainer.Parent.RegisteredTypes.CanResolve(registration, options.Clone());
}
internal object ConstructType(
Type implementationType,
ConstructorInfo constructor,
DependencyContainerResolveOptions options = null)
{
var typeToConstruct = implementationType;
if (constructor == null)
{
// Try and get the best constructor that we can construct
// if we can't construct any then get the constructor
// with the least number of parameters so we can throw a meaningful
// resolve exception
constructor = GetBestConstructor(typeToConstruct, options) ??
GetTypeConstructors(typeToConstruct).LastOrDefault();
}
if (constructor == null)
throw new DependencyContainerResolutionException(typeToConstruct);
var ctorParams = constructor.GetParameters();
var args = new object[ctorParams.Length];
for (var parameterIndex = 0; parameterIndex < ctorParams.Length; parameterIndex++)
{
var currentParam = ctorParams[parameterIndex];
try
{
args[parameterIndex] = options?.ConstructorParameters.GetValueOrDefault(currentParam.Name, ResolveInternal(new DependencyContainer.TypeRegistration(currentParam.ParameterType), options.Clone()));
}
catch (DependencyContainerResolutionException ex)
{
// If a constructor parameter can't be resolved
// it will throw, so wrap it and throw that this can't
// be resolved.
throw new DependencyContainerResolutionException(typeToConstruct, ex);
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(typeToConstruct, ex);
}
}
try
{
return CreateObjectConstructionDelegateWithCache(constructor).Invoke(args);
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(typeToConstruct, ex);
}
}
private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor)
{
if (ObjectConstructorCache.TryGetValue(constructor, out var objectConstructor))
return objectConstructor;
// We could lock the cache here, but there's no real side
// effect to two threads creating the same ObjectConstructor
// at the same time, compared to the cost of a lock for
// every creation.
var constructorParams = constructor.GetParameters();
var lambdaParams = Expression.Parameter(typeof(object[]), "parameters");
var newParams = new Expression[constructorParams.Length];
for (var i = 0; i < constructorParams.Length; i++)
{
var paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i));
newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType);
}
var newExpression = Expression.New(constructor, newParams);
var constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams);
objectConstructor = (ObjectConstructor)constructionLambda.Compile();
ObjectConstructorCache[constructor] = objectConstructor;
return objectConstructor;
}
private static IEnumerable<ConstructorInfo> GetTypeConstructors(Type type)
=> type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length);
private static bool IsAutomaticLazyFactoryRequest(Type type)
{
if (!type.IsGenericType())
return false;
var genericType = type.GetGenericTypeDefinition();
// Just a func
if (genericType == typeof(Func<>))
return true;
// 2 parameter func with string as first parameter (name)
if (genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(string))
return true;
// 3 parameter func with string as first parameter (name) and IDictionary<string, object> as second (parameters)
return genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) &&
type.GetGenericArguments()[1] == typeof(IDictionary<string, object>);
}
private ObjectFactoryBase GetParentObjectFactory(DependencyContainer.TypeRegistration registration)
{
if (_dependencyContainer.Parent == null)
return null;
return _dependencyContainer.Parent.RegisteredTypes.TryGetValue(registration, out var factory)
? factory.GetFactoryForChildContainer(registration.Type, _dependencyContainer.Parent, _dependencyContainer)
: _dependencyContainer.Parent.RegisteredTypes.GetParentObjectFactory(registration);
}
private ConstructorInfo GetBestConstructor(
Type type,
DependencyContainerResolveOptions options)
=> type.IsValueType() ? null : GetTypeConstructors(type).FirstOrDefault(ctor => CanConstruct(ctor, options));
private bool CanConstruct(
ConstructorInfo ctor,
DependencyContainerResolveOptions options)
{
foreach (var parameter in ctor.GetParameters())
{
if (string.IsNullOrEmpty(parameter.Name))
return false;
var isParameterOverload = options.ConstructorParameters.ContainsKey(parameter.Name);
if (parameter.ParameterType.IsPrimitive() && !isParameterOverload)
return false;
if (!isParameterOverload &&
!CanResolve(new DependencyContainer.TypeRegistration(parameter.ParameterType), options.Clone()))
return false;
}
return true;
}
private IEnumerable<DependencyContainer.TypeRegistration> GetParentRegistrationsForType(Type resolveType)
=> _dependencyContainer.Parent == null
? new DependencyContainer.TypeRegistration[] { }
: _dependencyContainer.Parent.RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(_dependencyContainer.Parent.RegisteredTypes.GetParentRegistrationsForType(resolveType));
}
}