/// <summary>
        /// Creates and returns an instance of a class or interface T with given constructor arguments (<paramref name="data"/>).
        /// </summary>
        /// <typeparam name="T">type of the interface to instantiate</typeparam>
        /// <param name="instantiatorKey"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        /// <exception cref="InstantiatorException"></exception>
        public T Instantiate(InstantiatorKey instantiatorKey, params object[] data)
        {
            try
            {
                var instance = CreateInstance(instantiatorKey, data);
                if (instance != null)
                {
                    return(instance);
                }

                // OK. We did not find an instantiator. Let's try to create one. First of all let's lock an object
                var lockObject1 = new object();
                lock (lockObject1)
                {
                    if (InstantiatorLocks.TryAdd(instantiatorKey, lockObject1))
                    {
                        // if we ended up here, it means that we were first
                        Instantiators.AddRange(CreateInstantiatorsForPackage(instantiatorKey));
                    }
                    else
                    {
                        // some other process have already created (or creating) instantiator
                        // Theoretically, it is quite possible to have previous process fail, so we will need to be careful about assuming that if we got here,
                        // then we should have instantiators.
                        lock (InstantiatorLocks[instantiatorKey])
                        {
                            // try read from the instantiators first. Maybe it has already been successfully created
                            instance = CreateInstance(instantiatorKey, data);
                            if (instance != null)
                            {
                                return(instance);
                            }
                            Instantiators.AddRange(CreateInstantiatorsForPackage(instantiatorKey));
                        }
                    }
                    instance = CreateInstance(instantiatorKey, data);
                    if (instance != null)
                    {
                        return(instance);
                    }
                }
            }
            catch (Exception e)
            {
                throw new InstantiatorException("Error occurred during instantiation", e);
            }

            throw new InstantiatorException($"Unknown error. Instantiator failed to produce an instance of {instantiatorKey}", null);
        }
        /// <summary>
        /// Creates an instance of a generic type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="instantiatorKey"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        private static T CreateInstance(InstantiatorKey instantiatorKey, object[] data)
        {
            if (!Instantiators.ContainsKey(instantiatorKey))
            {
                return(default(T));
            }

            var instantiatorByType = Instantiators[instantiatorKey];

            // here it make sense to concatenate params
            var paramsHash = data == null || !data.Any() ? "" : string.Join(", ", data.Select(d => d.GetType().FullName));

            if (instantiatorByType.ContainsKey(paramsHash))
            {
                return(instantiatorByType[paramsHash](data));
            }

            throw new InstantiatorException(
                      $"Constructor signature {paramsHash} not found for package {instantiatorKey}", null);
        }
 /// <summary>
 /// Creates and returns an instance of a class or interface T with a single constructor argument (<paramref name="data"/>).
 /// </summary>
 /// <typeparam name="T">type of the interface to instantiate</typeparam>
 /// <param name="instantiatorKey"></param>
 /// <param name="data"></param>
 /// <returns></returns>
 /// <exception cref="InstantiatorException"></exception>
 public T Instantiate(InstantiatorKey instantiatorKey, object data)
 {
     return(Instantiate(instantiatorKey, new[] { data }));
 }
 /// <summary>
 /// Creates and returns an instance of a class or interface T with no arguments.
 /// </summary>
 /// <typeparam name="T">type of the interface to instantiate</typeparam>
 /// <param name="instantiatorKey"></param>
 /// <returns></returns>
 /// <exception cref="InstantiatorException"></exception>
 public T Instantiate(InstantiatorKey instantiatorKey)
 {
     return(Instantiate(instantiatorKey, null));
 }
        /// <summary>
        /// Creates instantiators for all of the types in the package that can be instantiated
        /// </summary>
        /// <param name="instantiatorKey"></param>
        /// <returns></returns>
        /// <exception cref="InstantiatorCreationException"></exception>
        private Dictionary <InstantiatorKey, Dictionary <string, Instantiator <T> > > CreateInstantiatorsForPackage(InstantiatorKey instantiatorKey)
        {
            string packagePath;

            Directory.CreateDirectory(_rootPath);
            var returnDictionary = new Dictionary <InstantiatorKey, Dictionary <string, Instantiator <T> > >();

            try
            {
                packagePath = _packageRetriever.Retrieve(_rootPath, instantiatorKey.PackageId,
                                                         SemanticVersion.Parse(instantiatorKey.Version));
            }
            catch (Exception e)
            {
                throw new InstantiatorCreationException(
                          $"Package Retriever Failed to obtain the package {instantiatorKey.PackageId}.{instantiatorKey.Version}",
                          e);
            }

            if (string.IsNullOrWhiteSpace(packagePath))
            {
                throw new InstantiatorCreationException(
                          $"Package Retriever Failed to obtain the package {instantiatorKey.PackageId}.{instantiatorKey.Version} from available sources",
                          null);
            }

            // find the directory where the dlls are
            var libPath = Path.Combine(packagePath, "impromptu");

            var hotAssemblies = PluginContext <T> .DiscoverHotAssemblies(libPath);

            if (hotAssemblies == null)
            {
                return(returnDictionary);
            }

            foreach (var hotType in hotAssemblies.SelectMany(impromptu => impromptu.ExportedTypes.Where(
                                                                 t =>
                                                                 t.IsClass &&
                                                                 typeof(T).IsAssignableFrom(t) &&
                                                                 t.GetConstructors().Any())))
            {
                returnDictionary.Add(
                    new InstantiatorKey(instantiatorKey.PackageId, instantiatorKey.Version, hotType.FullName),
                    hotType.GetConstructors().ToDictionary(
                        ctor =>
                        !ctor.GetParameters().Any()
                                ? ""
                                : string.Join(", ", ctor.GetParameters().Select(p => p.ParameterType.FullName)),
                        CreateInstantiator));
            }

            return(returnDictionary);
        }