/// <summary>
        /// Specify a converter that will used whenever either a TSource-to-TDest translation is requested or a translation where properties must
        /// be translated from TSource to TDest (or from IEnumerable sets of TSource to TDest). Any converter that was previously available for
        /// this translation will be superceded by the one specified here (subsequent calls to this method with the same TSource and TDest
        /// will then take precedence again).
        /// </summary>
        public void SetConverter <TSource, TDest>(ICompilableTypeConverter <TSource, TDest> converter)
        {
            if (converter == null)
            {
                throw new ArgumentNullException("converter");
            }

            // For the ExtendableCompilableTypeConverterFactories, we just call AddNewConverter which will register the new converter before
            // any other converters which may handle the same translation. This sidesteps any complicated logic that might be involved in
            // trying to identify any converters from its repertoire that may need to be removed, this way the new converter will just
            // take precedence and any others will be ignored.
            _constructorBasedConverterFactory    = _constructorBasedConverterFactory.AddNewConverter <TSource, TDest>(converter);
            _propertySetterBasedConverterFactory = _propertySetterBasedConverterFactory.AddNewConverter <TSource, TDest>(converter);

            // For the internal cache (which will handle requests for translations of TSource-to-TDest, as opposed to translations which
            // require property retrieval from TSource to populate a property of constructor argument of TDest), we just set the value
            // on the dictionary, overwriting anything there before or adding a new item if not overwriting anything pre-existing.
            _converterCache[Tuple.Create(typeof(TSource), typeof(TDest))] = converter;
        }
        public ConverterWrapper(ByPropertySettingNullSourceBehaviourOptions nullSourceBehaviour, EnumerableSetNullHandlingOptions enumerableSetNullHandling)
        {
            if (!Enum.IsDefined(typeof(ByPropertySettingNullSourceBehaviourOptions), nullSourceBehaviour))
            {
                throw new ArgumentOutOfRangeException("nullSourceBehaviour");
            }
            if (!Enum.IsDefined(typeof(EnumerableSetNullHandlingOptions), enumerableSetNullHandling))
            {
                throw new ArgumentOutOfRangeException("enumerableSetNullHandling");
            }

            _nullSourceBehaviour       = nullSourceBehaviour;
            _enumerableSetNullHandling = enumerableSetNullHandling;
            _allPropertiesToIgnoreToPropertySetterConversions      = new List <PropertyInfo>();
            _allInitialisedFlagsIfTranslatingNullsToEmptyInstances = new List <PropertyInfo>();
            _converterCache = new Dictionary <Tuple <Type, Type>, object>();

            // Prepare converter factories (for by-constructor and by-property-setters) using the base types (AssignableType and
            // EnumConversion property getter factories)
            var nameMatcher = new CaseInsensitiveSkipUnderscoreNameMatcher();
            var basePropertyGetterFactories = new ICompilablePropertyGetterFactory[]
            {
                new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
                new CompilableEnumConversionPropertyGetterFactory(nameMatcher)
            };

            _constructorBasedConverterFactory = ExtendableCompilableTypeConverterFactoryHelpers.GenerateConstructorBasedFactory(
                nameMatcher,
                new ArgsLengthTypeConverterPrioritiserFactory(),
                basePropertyGetterFactories,
                _enumerableSetNullHandling
                );
            _propertySetterBasedConverterFactory = ExtendableCompilableTypeConverterFactoryHelpers.GeneratePropertySetterBasedFactory(
                nameMatcher,
                CompilableTypeConverterByPropertySettingFactory.PropertySettingTypeOptions.MatchAll,
                basePropertyGetterFactories,
                _allPropertiesToIgnoreToPropertySetterConversions,
                _nullSourceBehaviour,
                _allInitialisedFlagsIfTranslatingNullsToEmptyInstances,
                _enumerableSetNullHandling
                );
        }
        /// <summary>
        /// This will throw an exception if unable to generate a converter for request TSource and TDest pair, it will never return null
        /// </summary>
        public ICompilableTypeConverter <TSource, TDest> GetConverter <TSource, TDest>(
            IEnumerable <PropertyInfo> propertiesToIgnoreIfSettingPropertiesOnTDest,
            IEnumerable <PropertyInfo> initialisedFlagsIfTranslatingNullsToEmptyInstances,
            ConverterOverrideBehaviourOptions converterOverrideBehaviour)
        {
            if (propertiesToIgnoreIfSettingPropertiesOnTDest == null)
            {
                throw new ArgumentNullException("propertiesToIgnoreIfSettingPropertiesOnTDest");
            }
            var propertiesToIgnoreList = propertiesToIgnoreIfSettingPropertiesOnTDest.ToList();

            if (propertiesToIgnoreList.Any(p => p == null))
            {
                throw new ArgumentException("Null reference encountered in propertiesToIgnoreIfSettingPropertiesOnTDest set ");
            }
            if (initialisedFlagsIfTranslatingNullsToEmptyInstances == null)
            {
                throw new ArgumentNullException("initialisedFlagsIfTranslatingNullsToEmptyInstances");
            }
            var initialisedFlagsIfTranslatingNullsToEmptyInstancesList = initialisedFlagsIfTranslatingNullsToEmptyInstances.ToList();

            if (initialisedFlagsIfTranslatingNullsToEmptyInstances.Any(p => p == null))
            {
                throw new ArgumentException("Null reference encountered in initialisedFlagsIfTranslatingNullsToEmptyInstances set ");
            }
            if (!Enum.IsDefined(typeof(ConverterOverrideBehaviourOptions), converterOverrideBehaviour))
            {
                throw new ArgumentOutOfRangeException("converterOverrideBehaviour");
            }

            var cacheKey = Tuple.Create(typeof(TSource), typeof(TDest));

            if (converterOverrideBehaviour != ConverterOverrideBehaviourOptions.IgnoreCache)
            {
                object unTypedCachedResult;
                if (_converterCache.TryGetValue(cacheKey, out unTypedCachedResult))
                {
                    if (converterOverrideBehaviour == ConverterOverrideBehaviourOptions.UseAnyExistingConverter)
                    {
                        return((ICompilableTypeConverter <TSource, TDest>)unTypedCachedResult);
                    }
                    _converterCache.Remove(cacheKey);
                }
            }

            // If there are any properties-to-ignore specified then add them to the total combined list and re-generate the
            // _propertySetterBasedConverterFactory reference to take them into account. Also add any initialised-flags to the ignore list
            // since these are not expected to be mapped from the source data, these flags are expected to be set afterwards (with false
            // if the source reference was null and true if not)
            if (propertiesToIgnoreList.Any() || initialisedFlagsIfTranslatingNullsToEmptyInstances.Any())
            {
                _allPropertiesToIgnoreToPropertySetterConversions.AddRange(propertiesToIgnoreIfSettingPropertiesOnTDest);
                _allPropertiesToIgnoreToPropertySetterConversions.AddRange(initialisedFlagsIfTranslatingNullsToEmptyInstances);
                _allInitialisedFlagsIfTranslatingNullsToEmptyInstances.AddRange(initialisedFlagsIfTranslatingNullsToEmptyInstances);
                var currentByPropertySetterConvererFactoryConfigurationData = _propertySetterBasedConverterFactory.GetConfigurationData();
                _propertySetterBasedConverterFactory = new ExtendableCompilableTypeConverterFactory(
                    currentByPropertySetterConvererFactoryConfigurationData.NameMatcher,
                    currentByPropertySetterConvererFactoryConfigurationData.BasePropertyGetterFactories,
                    propertyGetterFactories => new CompilableTypeConverterByPropertySettingFactory(
                        new CombinedCompilablePropertyGetterFactory(propertyGetterFactories),
                        CompilableTypeConverterByPropertySettingFactory.PropertySettingTypeOptions.MatchAll,
                        _allPropertiesToIgnoreToPropertySetterConversions,
                        _nullSourceBehaviour,
                        _allInitialisedFlagsIfTranslatingNullsToEmptyInstances
                        ),
                    currentByPropertySetterConvererFactoryConfigurationData.PropertyGetterFactoryExtrapolator
                    );
            }

            // Attempt to generate a converter using the _constructorBasedConverterFactory and then the _propertySetterBasedConverterFactory.
            // If both fail then it's difficult to know for sure which exception is most useful to allow through. If the destination type has
            // a parameter-less constructor then allow the by-property-setter exception to be raised (if there was no parameter-less constructor
            // then the by-property-setter factory wouldn't have been able to do anything and we'll assume that the by-constructor factory
            // exception is more useful).
            ICompilableTypeConverter <TSource, TDest> converter;
            Exception mappingException;

            try
            {
                converter = _constructorBasedConverterFactory.Get <TSource, TDest>();
            }
            catch (Exception byConstructorException)
            {
                try
                {
                    converter = _propertySetterBasedConverterFactory.Get <TSource, TDest>();
                }
                catch (Exception byPropertySettingException)
                {
                    if (typeof(TDest).GetConstructor(new Type[0]) != null)
                    {
                        mappingException = byPropertySettingException;
                    }
                    else
                    {
                        mappingException = byConstructorException;
                    }
                    throw mappingException;
                }
            }

            _constructorBasedConverterFactory    = _constructorBasedConverterFactory.AddNewConverter(converter);
            _propertySetterBasedConverterFactory = _propertySetterBasedConverterFactory.AddNewConverter(converter);
            if (converterOverrideBehaviour != ConverterOverrideBehaviourOptions.IgnoreCache)
            {
                _converterCache[cacheKey] = converter;
            }
            return(converter);
        }