Skip to content

Commit 761be9e

Browse files
authored
Add type checking to constructor parameters (#9)
1 parent ed8bc6b commit 761be9e

File tree

9 files changed

+239
-72
lines changed

9 files changed

+239
-72
lines changed

nanoFramework.DependencyInjection/DependencyInjection/ServiceDescriptor.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ public override string ToString()
108108
return lifetime + $"{nameof(ImplementationInstance)}: {ImplementationInstance}";
109109
}
110110

111-
internal Type GetImplementationType()
111+
/// <summary>
112+
/// Returns the <see cref="Type"/> implementing the instance.
113+
/// </summary>
114+
public Type GetImplementationType()
112115
{
113116
if (ImplementationType != null)
114117
{

nanoFramework.DependencyInjection/DependencyInjection/ServiceProviderEngine.cs

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ internal void ValidateService(ServiceDescriptor descriptor)
3636
/// </summary>
3737
internal void DisposeServices()
3838
{
39-
for (int i = Services.Count - 1; i >= 0; i--)
39+
for (int index = Services.Count - 1; index >= 0; index--)
4040
{
41-
if (Services[i].ImplementationInstance is IDisposable disposable)
41+
if (Services[index].ImplementationInstance is IDisposable disposable)
4242
{
4343
disposable.Dispose();
4444
}
@@ -160,7 +160,8 @@ private object Resolve(Type implementationType)
160160

161161
if (constructorParameters == null)
162162
{
163-
throw new InvalidOperationException();
163+
throw new InvalidOperationException(
164+
$"Constructor for '{implementationType}' could not be located.");
164165
}
165166

166167
if (constructorParameters.Length == 0)
@@ -176,18 +177,19 @@ private object Resolve(Type implementationType)
176177
{
177178
var parameterType = constructorParameters[index].ParameterType;
178179

179-
if (TryBindToPrimitive(parameterType, out object defaultType))
180+
if (parameterType.IsResolvable())
180181
{
181182
types[index] = parameterType;
182-
parameters[index] = defaultType;
183+
parameters[index] = GetResolvableDefault(parameterType);
183184
}
184185
else
185186
{
186187
var service = GetService(parameterType);
187188

188189
if (service == null)
189190
{
190-
throw new InvalidOperationException($"'{implementationType}'->'{parameterType}'.");
191+
throw new InvalidOperationException(
192+
$"Unable to resolve service for '{parameterType}'.");
191193
}
192194

193195
types[index] = parameterType;
@@ -220,18 +222,34 @@ private ParameterInfo[] GetParameters(Type implementationType)
220222
{
221223
if (constructors[j].GetParameters().Length == constructors[j + 1].GetParameters().Length)
222224
{
223-
throw new InvalidOperationException();
225+
throw new InvalidOperationException(
226+
$"Multiple constructors found in '{implementationType}'.");
224227
}
225228
}
226229
}
227230

228-
// step 2: get the constructor with the most parameters
231+
// step 2: get the constructor with the most resolvable parameters
229232
foreach (ConstructorInfo constructor in constructors)
230233
{
231234
ParameterInfo[] parameters = constructor.GetParameters();
232235

233236
int length = parameters.Length;
234237

238+
foreach (ParameterInfo parameter in parameters)
239+
{
240+
Type type = parameter.ParameterType;
241+
242+
if (type.IsResolvable())
243+
{
244+
// check for simple binding first
245+
}
246+
else if (GetService(type) == null)
247+
{
248+
// binding could not be resolved ingore constructor
249+
length = -1;
250+
}
251+
}
252+
235253
if (bestLength < length)
236254
{
237255
bestLength = length;
@@ -243,49 +261,30 @@ private ParameterInfo[] GetParameters(Type implementationType)
243261
}
244262

245263
/// <summary>
246-
/// Try and bind to a primitive type.
264+
/// Get primitive default type.
247265
/// </summary>
248-
private static bool TryBindToPrimitive(Type type, out object defaultType)
266+
private static object GetResolvableDefault(Type type)
249267
{
250-
defaultType = null;
251-
252-
// This list dosn't match the binding list below because
268+
// This list dosn't match the IsResolvable() because
253269
// we only check for items we know are not null by default
254-
if (type == typeof(object)) defaultType = default;
255-
if (type == typeof(int)) defaultType = default(int);
256-
if (type == typeof(uint)) defaultType = default(uint);
257-
if (type == typeof(bool)) defaultType = default(bool);
258-
if (type == typeof(char)) defaultType = default(char);
259-
if (type == typeof(byte)) defaultType = default(byte);
260-
if (type == typeof(sbyte)) defaultType = default(sbyte);
261-
if (type == typeof(short)) defaultType = default(short);
262-
if (type == typeof(ushort)) defaultType = default(ushort);
263-
if (type == typeof(long)) defaultType = default(long);
264-
if (type == typeof(ulong)) defaultType = default(ulong);
265-
if (type == typeof(double)) defaultType = default(double);
266-
if (type == typeof(Guid)) defaultType = default(Guid);
267-
if (type == typeof(DateTime)) defaultType = default(DateTime);
268-
if (type == typeof(TimeSpan)) defaultType = default(TimeSpan);
269-
270-
return type == typeof(object)
271-
|| type == typeof(string)
272-
|| type == typeof(int)
273-
|| type == typeof(uint)
274-
|| type == typeof(bool)
275-
|| type == typeof(char)
276-
|| type == typeof(byte)
277-
|| type == typeof(sbyte)
278-
|| type == typeof(short)
279-
|| type == typeof(ushort)
280-
|| type == typeof(long)
281-
|| type == typeof(ulong)
282-
|| type == typeof(double)
283-
|| type == typeof(Guid)
284-
|| type == typeof(DateTime)
285-
|| type == typeof(TimeSpan)
286-
|| type == typeof(Enum)
287-
|| type == typeof(Array)
288-
|| type == typeof(ArrayList);
270+
if (type == typeof(object)) return default;
271+
if (type == typeof(int)) return default(int);
272+
if (type == typeof(uint)) return default(uint);
273+
if (type == typeof(bool)) return default(bool);
274+
if (type == typeof(char)) return default(char);
275+
if (type == typeof(byte)) return default(byte);
276+
if (type == typeof(sbyte)) return default(sbyte);
277+
if (type == typeof(short)) return default(short);
278+
if (type == typeof(ushort)) return default(ushort);
279+
if (type == typeof(long)) return default(long);
280+
if (type == typeof(ulong)) return default(ulong);
281+
if (type == typeof(double)) return default(double);
282+
if (type == typeof(float)) return default(float);
283+
if (type == typeof(Guid)) return default(Guid);
284+
if (type == typeof(DateTime)) return default(DateTime);
285+
if (type == typeof(TimeSpan)) return default(TimeSpan);
286+
287+
return null;
289288
}
290289
}
291290
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Collections;
3+
4+
namespace nanoFramework.DependencyInjection
5+
{
6+
/// <summary>
7+
/// Contains extension methods for <see cref="Type"/>.
8+
/// </summary>
9+
internal static class TypeExtensions
10+
{
11+
/// <summary>
12+
/// Compares this instance to a specified type and returns an indication if resolvable value.
13+
/// </summary>
14+
/// <param name="type">The current <see cref="Type"/></param>
15+
public static bool IsResolvable(this Type type)
16+
{
17+
return type == typeof(string)
18+
|| type == typeof(bool)
19+
|| type == typeof(byte)
20+
|| type == typeof(sbyte)
21+
|| type == typeof(short)
22+
|| type == typeof(ushort)
23+
|| type == typeof(int)
24+
|| type == typeof(uint)
25+
|| type == typeof(long)
26+
|| type == typeof(ulong)
27+
|| type == typeof(double)
28+
|| type == typeof(float)
29+
|| type == typeof(object)
30+
|| type == typeof(DateTime)
31+
|| type == typeof(TimeSpan)
32+
|| type == typeof(Guid)
33+
|| type == typeof(Array)
34+
|| type == typeof(ArrayList);
35+
}
36+
}
37+
}

nanoFramework.DependencyInjection/nanoFramework.DependencyInjection.nfproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<Compile Include="DependencyInjection\ActivatorUtilities.cs" />
3333
<Compile Include="DependencyInjection\IServiceCollection.cs" />
3434
<Compile Include="DependencyInjection\ServiceProviderEngine.cs" />
35+
<Compile Include="DependencyInjection\TypeExtensions.cs" />
3536
<Compile Include="Properties\AssemblyInfo.cs" />
3637
<Compile Include="DependencyInjection\ServiceCollection.cs" />
3738
<Compile Include="DependencyInjection\ServiceCollectionContainerBuilderExtensions.cs" />

nanoFramework.DependencyInjection/packages.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
<packages>
33
<package id="nanoFramework.CoreLibrary" version="1.12.0" targetFramework="netnano1.0" />
44
<package id="Nerdbank.GitVersioning" version="3.5.107" developmentDependency="true" targetFramework="netnano1.0" />
5-
</packages>
5+
</packages>

tests/DependencyInjectionTests.cs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ public void ServicesRegisteredWithImplementationTypeForTransientServices()
4343
// Assert.IsType(typeof(StructFakeService), service.GetType());
4444
//}
4545

46-
//TODO: Reflection is not resolving with the IsArray flag
47-
// https://github.com/nanoframework/Home/issues/1086
48-
4946
[TestMethod]
5047
public void ServiceInstanceWithPrimitiveBinding()
5148
{
@@ -55,13 +52,28 @@ public void ServiceInstanceWithPrimitiveBinding()
5552

5653
var service = (ClassWithPrimitiveBinding)serviceProvider.GetService(typeof(ClassWithPrimitiveBinding));
5754

58-
Assert.Null(service.Obj);
5955
Assert.Null(service.Str);
60-
Assert.Equal(0, service.Integer);
61-
Assert.Equal(false, service.Boolean);
62-
//Assert.Null(service.Characters); // array is not resolving correctly
56+
Assert.Null(service.Obj);
6357
Assert.NotNull(service.Guid);
64-
Assert.NotNull(service.Time);
58+
59+
Assert.True(service.Boolean == false);
60+
Assert.True(service.Short == 0);
61+
Assert.True(service.Ushort == 0);
62+
Assert.True(service.Int == 0);
63+
Assert.True(service.UInt == 0);
64+
Assert.True(service.Long == 0);
65+
Assert.True(service.Ulong == 0);
66+
Assert.True(service.Double == 0);
67+
Assert.True(service.Float == 0);
68+
Assert.True(service.Byte == new byte());
69+
Assert.True(service.SByte == new sbyte());
70+
Assert.True(service.DateTime == new DateTime());
71+
Assert.True(service.TimeSpan == new TimeSpan());
72+
Assert.True(service.Array == default);
73+
Assert.True(service.ArrayList == default);
74+
75+
//TODO: Add array types - reflection is not resolving with the IsArray flag
76+
// https://github.com/nanoframework/Home/issues/1086
6577
}
6678

6779
[TestMethod]
@@ -212,6 +224,19 @@ public void DoesNotAllowForAmbiguousConstructorMatches()
212224
);
213225
}
214226

227+
[TestMethod]
228+
public void IngoreUnresolvedMultipleConstructorMatches()
229+
{
230+
var serviceProvider = new ServiceCollection()
231+
.AddSingleton(typeof(IFakeService), typeof(FakeService))
232+
.AddSingleton(typeof(ClassWithMultipleCtors))
233+
.BuildServiceProvider();
234+
235+
var service = (ClassWithMultipleCtors)serviceProvider.GetService(typeof(ClassWithMultipleCtors));
236+
237+
Assert.Equal(2, service.CtorUsed);
238+
}
239+
215240
[TestMethod]
216241
public void SelfResolveThenDispose()
217242
{
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Copyright (c) .NET Foundation and Contributors
3+
// See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System.Collections;
7+
8+
namespace nanoFramework.DependencyInjection.UnitTests.Fakes
9+
{
10+
public class ClassWithMultipleCtors
11+
{
12+
public ClassWithMultipleCtors(string data)
13+
{
14+
CtorUsed = 0;
15+
}
16+
17+
public ClassWithMultipleCtors(IFakeService service, string data)
18+
{
19+
CtorUsed = 1;
20+
}
21+
22+
public ClassWithMultipleCtors(IFakeService service, string data1, int data2)
23+
{
24+
FakeService = service;
25+
Data1 = data1;
26+
Data2 = data2;
27+
28+
CtorUsed = 2;
29+
}
30+
31+
public ClassWithMultipleCtors(IFakeService service, ICollection collection, string data1, int data2)
32+
{
33+
FakeService = service;
34+
Data1 = data1;
35+
Data2 = data2;
36+
Collection = collection;
37+
CtorUsed = 3;
38+
}
39+
40+
public IFakeService FakeService { get; }
41+
42+
public string Data1 { get; }
43+
44+
public int Data2 { get; }
45+
46+
public int CtorUsed { get; set; }
47+
48+
public ICollection Collection { get; set; }
49+
}
50+
}

0 commit comments

Comments
 (0)