/src/LinFu.IoC/Configuration/Resolvers/MethodFinder.cs
C# | 208 lines | 119 code | 31 blank | 58 comment | 15 complexity | e09b2aebfa37f65dd27f0c5c4d3e9fd9 MD5 | raw file
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Reflection; 5using LinFu.Finders; 6using LinFu.Finders.Interfaces; 7using LinFu.IoC.Configuration.Interfaces; 8using LinFu.IoC.Interfaces; 9 10namespace LinFu.IoC.Configuration 11{ 12 /// <summary> 13 /// Represents a class that determines which method best matches the 14 /// services currently in the target container. 15 /// </summary> 16 /// <typeparam name="T">The method type to search.</typeparam> 17 public class MethodFinder<T> : IMethodFinder<T> 18 where T : MethodBase 19 { 20 /// <summary> 21 /// Determines which method best matches the 22 /// services currently in the target container. 23 /// </summary> 24 /// <param name="items">The list of methods to search.</param> 25 /// <param name="finderContext">The <see cref="IMethodFinderContext" /> that describes the target method.</param> 26 /// <returns> 27 /// Returns the method with the most resolvable parameters from the target <see cref="IServiceContainer" /> 28 /// instance. 29 /// </returns> 30 public T GetBestMatch(IEnumerable<T> items, IMethodFinderContext finderContext) 31 { 32 T bestMatch = null; 33 var fuzzyList = items.AsFuzzyList(); 34 35 // Return the first item 36 // if there is no other alternative 37 if (fuzzyList.Count == 1) 38 return fuzzyList[0].Item; 39 40 var additionalArguments = finderContext.Arguments; 41 var additionalArgumentTypes = (from argument in additionalArguments 42 let argumentType = 43 argument == null ? typeof(object) : argument.GetType() 44 select argumentType).ToList(); 45 46 Rank(fuzzyList, finderContext); 47 48 // Match the generic parameter types 49 var genericParameterCount = finderContext.TypeArguments != null ? finderContext.TypeArguments.Count() : 0; 50 Func<T, bool> matchGenericArgumentCount = method => 51 { 52 var genericArguments = method.IsGenericMethod 53 ? method.GetGenericArguments 54 () 55 : new Type[0]; 56 var currentParameterCount = genericArguments.Count(); 57 58 return genericParameterCount == currentParameterCount; 59 }; 60 fuzzyList.AddCriteria(matchGenericArgumentCount, CriteriaType.Critical); 61 62 var candidates = fuzzyList.Where(fuzzy => fuzzy.Confidence > 0); 63 bestMatch = SelectBestMatch(candidates); 64 65 // If all else fails, find the method 66 // that matches only the additional arguments 67 if (bestMatch != null) 68 return bestMatch; 69 70 return GetNextBestMatch(fuzzyList, additionalArgumentTypes, bestMatch); 71 } 72 73 74 private T GetNextBestMatch(IList<IFuzzyItem<T>> fuzzyList, List<Type> additionalArgumentTypes, T bestMatch) 75 { 76 var additionalArgumentCount = additionalArgumentTypes.Count; 77 fuzzyList.Reset(); 78 79 // Match the number of arguments 80 Func<T, bool> matchParameterCount = method => 81 { 82 var parameters = method.GetParameters(); 83 var parameterCount = parameters != null 84 ? parameters.Length 85 : 0; 86 87 return parameterCount == additionalArgumentCount; 88 }; 89 90 // Remove any methods that do not match 91 // the parameter count 92 fuzzyList.AddCriteria(matchParameterCount, CriteriaType.Critical); 93 94 CheckArguments(fuzzyList, additionalArgumentTypes); 95 var nextBestMatch = fuzzyList.BestMatch(); 96 97 if (nextBestMatch == null) 98 return null; 99 100 return nextBestMatch.Item; 101 } 102 103 /// <summary> 104 /// Determines which item among the <paramref name="candidates" /> is the best match. 105 /// </summary> 106 /// <param name="candidates">The list of possible matches.</param> 107 /// <returns>The best match if found; otherwise, it should return <c>null</c>.</returns> 108 protected virtual T SelectBestMatch(IEnumerable<IFuzzyItem<T>> candidates) 109 { 110 var bestMatch = default(T); 111 // Since the remaining constructors all have 112 // parameter types that currently exist 113 // in the container as a service, 114 // the best match will be the constructor with 115 // the most parameters 116 var bestParameterCount = -1; 117 foreach (var candidate in candidates) 118 { 119 var currentItem = candidate.Item; 120 var parameters = currentItem.GetParameters(); 121 var parameterCount = parameters.Count(); 122 123 if (parameterCount <= bestParameterCount) 124 continue; 125 126 bestMatch = currentItem; 127 bestParameterCount = parameterCount; 128 } 129 130 return bestMatch; 131 } 132 133 /// <summary> 134 /// Adds additional <see cref="ICriteria{T}" /> to the fuzzy search list. 135 /// </summary> 136 /// <param name="methods">The list of methods to rank.</param> 137 /// <param name="finderContext">The <see cref="IMethodFinderContext" /> that describes the target method.</param> 138 protected virtual void Rank(IList<IFuzzyItem<T>> methods, IMethodFinderContext finderContext) 139 { 140 } 141 142 /// <summary> 143 /// Attempts to match the <paramref name="additionalArgumentTypes" /> against the 144 /// <paramref name="fuzzyList">list of methods</paramref>. 145 /// </summary> 146 /// <param name="fuzzyList">The list of items currently being compared.</param> 147 /// <param name="additionalArgumentTypes"> 148 /// The set of <see cref="Type" /> instances that describe each supplied argument 149 /// type. 150 /// </param> 151 private static void CheckArguments(IList<IFuzzyItem<T>> fuzzyList, 152 IEnumerable<Type> additionalArgumentTypes) 153 { 154 var argTypes = additionalArgumentTypes.ToArray(); 155 156 for (var i = 1; i <= argTypes.Length; i++) 157 { 158 var currentOffset = i; 159 var currentIndex = argTypes.Length - currentOffset; 160 var currentArgumentType = argTypes[currentIndex]; 161 Func<T, bool> hasCompatibleArgument = method => 162 { 163 var parameters = method.GetParameters(); 164 var parameterCount = parameters.Length; 165 var targetParameterIndex = currentIndex; 166 167 // Make sure that the index is valid 168 if (targetParameterIndex < 0 || 169 targetParameterIndex >= parameterCount) 170 return false; 171 172 // The parameter type must be compatible with the 173 // given argument type 174 var parameterType = 175 parameters[targetParameterIndex].ParameterType; 176 return parameterType.IsAssignableFrom(currentArgumentType); 177 }; 178 179 // Match each additional argument type to its 180 // relative position from the end of the parameter 181 // list 182 fuzzyList.AddCriteria(hasCompatibleArgument, CriteriaType.Critical); 183 184 Func<T, bool> hasExactArgumentType = method => 185 { 186 var parameters = method.GetParameters(); 187 var parameterCount = parameters.Length; 188 var targetParameterIndex = currentIndex; 189 190 // Make sure that the index is valid 191 if (targetParameterIndex < 0 || 192 targetParameterIndex >= parameterCount) 193 return false; 194 195 // The parameter type should match the 196 // given argument type 197 var parameterType = 198 parameters[targetParameterIndex].ParameterType; 199 return parameterType.IsAssignableFrom(currentArgumentType); 200 }; 201 202 // Make sure that the finder prefers exact 203 // type matches over compatible types 204 fuzzyList.AddCriteria(hasExactArgumentType, CriteriaType.Optional); 205 } 206 } 207 } 208}