Skip to content

Commit ca26c0e

Browse files
Merge pull request #4 from servicetitan/generic-interfaces-without-type-parameters
Add supporting of generic interfaces without type parameters
2 parents 307b2fd + 801b4ef commit ca26c0e

File tree

2 files changed

+84
-21
lines changed

2 files changed

+84
-21
lines changed

LazyProxy.Tests/LazyProxyBuilderTests.cs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Linq;
32
using System.Threading.Tasks;
43
using Moq;
54
using Xunit;
@@ -8,13 +7,21 @@ namespace LazyProxy.Tests
87
{
98
public class LazyProxyBuilderTests
109
{
11-
public class TestArgument { }
10+
public interface IBaseArgument { }
11+
12+
public abstract class BaseArgument : IBaseArgument { }
13+
14+
public abstract class BaseArgument2 { }
15+
16+
public struct TestArgument : IBaseArgument { }
1217

1318
// ReSharper disable once MemberCanBePrivate.Global
14-
public class TestArgument2 { }
19+
public class TestArgument2 : BaseArgument { }
1520

1621
// ReSharper disable once MemberCanBePrivate.Global
17-
public class TestArgument3 { }
22+
public class TestArgument3 : BaseArgument { }
23+
24+
public class TestArgument4 : BaseArgument2, IBaseArgument { }
1825

1926
private class TestException : Exception { }
2027

@@ -37,14 +44,21 @@ public interface ITestService : IParentTestService
3744
string MethodWithDefaultValue(string arg = "arg");
3845
string MethodWithOutValue(out string arg);
3946
string MethodWithRefValue(ref TestArgument arg);
40-
string GenericMethod<T1, T2>(string arg);
47+
string GenericMethod<T1, T2, T3>(string arg)
48+
where T1 : class, IBaseArgument, new()
49+
where T2 : struct
50+
where T3 : BaseArgument2, IBaseArgument;
4151
}
4252

4353
// ReSharper disable once MemberCanBePrivate.Global
4454
public interface IGenericTestService<T, in TIn, out TOut>
55+
where T : class, IBaseArgument, new()
56+
where TIn : struct
57+
where TOut : BaseArgument2, IBaseArgument
4558
{
4659
TOut Method1(T arg1, TIn arg2);
4760
T Method2(T arg1, TIn arg2);
61+
T GenericMethod<T1, T2>(TIn arg);
4862
}
4963

5064
// ReSharper disable once UnusedMember.Global
@@ -55,7 +69,7 @@ public void ProxyMustImplementInterface()
5569
{
5670
var proxyType = LazyProxyBuilder.BuildLazyProxyType<ITestService>();
5771

58-
Assert.True(proxyType.GetInterfaces().Contains(typeof(ITestService)));
72+
Assert.Contains(typeof(ITestService), proxyType.GetInterfaces());
5973
}
6074

6175
[Fact]
@@ -211,12 +225,14 @@ public void GenericMethodsMustBeProxied()
211225
{
212226
var mock = new Mock<ITestService>(MockBehavior.Strict);
213227

214-
mock.Setup(s => s.GenericMethod<TestArgument, TestException>(arg)).Returns(expectedResult);
228+
mock
229+
.Setup(s => s.GenericMethod<TestArgument2, TestArgument, TestArgument4>(arg))
230+
.Returns(expectedResult);
215231

216232
return mock.Object;
217233
});
218234

219-
var actualResult = proxy.GenericMethod<TestArgument, TestException>(arg);
235+
var actualResult = proxy.GenericMethod<TestArgument2, TestArgument, TestArgument4>(arg);
220236

221237
Assert.Equal(expectedResult, actualResult);
222238
}
@@ -325,14 +341,14 @@ public void ExceptionsFromServiceMustBeThrown()
325341
[Fact]
326342
public void GenericInterfacesMustBeProxied()
327343
{
328-
var arg1 = new TestArgument();
329-
var arg2 = new TestArgument2();
330-
var expectedResult1 = new TestArgument3();
331-
var expectedResult2 = new TestArgument();
344+
var arg1 = new TestArgument2();
345+
var arg2 = new TestArgument();
346+
var expectedResult1 = new TestArgument4();
347+
var expectedResult2 = new TestArgument2();
332348

333349
var proxy = LazyProxyBuilder.CreateLazyProxyInstance(() =>
334350
{
335-
var mock = new Mock<IGenericTestService<TestArgument, TestArgument2, TestArgument3>>(MockBehavior.Strict);
351+
var mock = new Mock<IGenericTestService<TestArgument2, TestArgument, TestArgument4>>(MockBehavior.Strict);
336352

337353
mock.Setup(s => s.Method1(arg1, arg2)).Returns(expectedResult1);
338354
mock.Setup(s => s.Method2(arg1, arg2)).Returns(expectedResult2);
@@ -352,8 +368,10 @@ public void GenericInterfaceWithDifferentTypeParametersMustBeCreatedWithoutExcep
352368
{
353369
var exception = Record.Exception(() =>
354370
{
355-
LazyProxyBuilder.BuildLazyProxyType<IGenericTestService<TestArgument, TestArgument2, TestArgument3>>();
356-
LazyProxyBuilder.BuildLazyProxyType<IGenericTestService<TestArgument3, TestArgument, TestArgument2>>();
371+
LazyProxyBuilder.BuildLazyProxyType(typeof(IGenericTestService<,,>));
372+
LazyProxyBuilder.BuildLazyProxyType<IGenericTestService<TestArgument2, TestArgument, TestArgument4>>();
373+
LazyProxyBuilder.BuildLazyProxyType<IGenericTestService<TestArgument3, TestArgument, TestArgument4>>();
374+
357375
});
358376

359377
Assert.Null(exception);

LazyProxy/LazyProxyBuilder.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public static T CreateLazyProxyInstance<T>(Func<T> valueFactory)
9191
///
9292
/// ]]>
9393
/// </summary>
94-
/// <typeparam name="T">The interface proxy type implements.</typeparam>
94+
/// <param name="type">The interface proxy type implements.</param>
9595
/// <returns>The lazy proxy type.</returns>
9696
private static Type DefineProxyType(Type type)
9797
{
@@ -102,13 +102,24 @@ private static Type DefineProxyType(Type type)
102102
var typeName = $"{type.Namespace}.{LazyProxyTypeSuffix}_{guid}_{type.Name}";
103103

104104
return ModuleBuilder.DefineType(typeName, TypeAttributes.Public)
105+
.AddGenericParameters(type)
105106
.AddInterface(type)
106107
.AddServiceField(type, out var serviceField)
107108
.AddConstructor(type, serviceField)
108109
.AddMethods(type, serviceField)
109110
.CreateTypeInfo();
110111
}
111112

113+
private static TypeBuilder AddGenericParameters(this TypeBuilder typeBuilder, Type type)
114+
{
115+
if (type.IsGenericTypeDefinition)
116+
{
117+
AddGenericParameters(type.GetGenericArguments, typeBuilder.DefineGenericParameters);
118+
}
119+
120+
return typeBuilder;
121+
}
122+
112123
private static TypeBuilder AddInterface(this TypeBuilder typeBuilder, Type type)
113124
{
114125
typeBuilder.AddInterfaceImplementation(type);
@@ -164,11 +175,7 @@ private static TypeBuilder AddMethods(this TypeBuilder typeBuilder, Type type, F
164175

165176
if (method.IsGenericMethod)
166177
{
167-
var genericTypeNames = method.GetGenericArguments()
168-
.Select(genericType => genericType.Name)
169-
.ToArray();
170-
171-
methodBuilder.DefineGenericParameters(genericTypeNames);
178+
AddGenericParameters(method.GetGenericArguments, methodBuilder.DefineGenericParameters);
172179
}
173180

174181
var generator = methodBuilder.GetILGenerator();
@@ -206,5 +213,43 @@ private static MethodInfo GetGetServiceValueMethod(FieldInfo serviceField)
206213
// ReSharper disable once PossibleNullReferenceException
207214
return serviceField.FieldType.GetProperty("Value").GetGetMethod(true);
208215
}
216+
217+
private static void AddGenericParameters(
218+
Func<IReadOnlyList<Type>> getGenericParameters,
219+
Func<string[], IReadOnlyList<GenericTypeParameterBuilder>> defineGenericParameters)
220+
{
221+
var genericParameters = getGenericParameters();
222+
223+
var genericParametersNames = genericParameters
224+
.Select(genericType => genericType.Name)
225+
.ToArray();
226+
227+
var definedGenericParameters = defineGenericParameters(genericParametersNames);
228+
229+
for (var i = 0; i < genericParameters.Count; i++)
230+
{
231+
var genericParameter = genericParameters[i];
232+
var definedGenericParameter = definedGenericParameters[i];
233+
var genericParameterAttributes = genericParameter.GenericParameterAttributes
234+
& ~GenericParameterAttributes.Covariant
235+
& ~GenericParameterAttributes.Contravariant;
236+
237+
definedGenericParameter.SetGenericParameterAttributes(genericParameterAttributes);
238+
239+
var genericParameterConstraints = genericParameter.GetGenericParameterConstraints();
240+
241+
foreach (var constraint in genericParameterConstraints)
242+
{
243+
if (constraint.IsInterface)
244+
{
245+
definedGenericParameter.SetInterfaceConstraints(constraint);
246+
}
247+
else
248+
{
249+
definedGenericParameter.SetBaseTypeConstraint(constraint);
250+
}
251+
}
252+
}
253+
}
209254
}
210255
}

0 commit comments

Comments
 (0)