Skip to content

Commit 5bc8c3e

Browse files
committed
fix CI + add tests for #631
1 parent 5e9c0bc commit 5bc8c3e

File tree

4 files changed

+224
-13
lines changed

4 files changed

+224
-13
lines changed

src/DryIoc/Container.cs

+15-11
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ IEnumerable<object> IResolver.ResolveMany(Type serviceType, object serviceKey,
628628
ServiceRegistrationInfo[] variantGenericItems = null;
629629
if (requiredItemType.IsGenericType && Rules.VariantGenericTypesInResolvedCollection)
630630
{
631-
variantGenericItems = GetServiceRegistrations(requiredItemType,
631+
variantGenericItems = GetServiceRegistrations(requiredItemType,
632632
static (ref Type reqItType, ref ServiceRegistrationInfo src, out ServiceRegistrationInfo res) =>
633633
{
634634
res = src;
@@ -1021,7 +1021,7 @@ public Factory GetServiceFactoryOrDefault(Request request)
10211021
serviceFactories = r.Services;
10221022

10231023
var rules = Rules;
1024-
if (rules.FactorySelector != null && serviceKey == null)
1024+
if (rules.FactorySelector != null & serviceKey == null)
10251025
return GetRuleSelectedServiceFactoryOrDefault(rules, serviceFactories, request, details, serviceType);
10261026

10271027
var entry = serviceFactories.GetValueOrDefault(serviceType);
@@ -1043,7 +1043,7 @@ public Factory GetServiceFactoryOrDefault(Request request)
10431043
{
10441044
if (e.Value is Factory f)
10451045
{
1046-
if ((serviceKey == null || serviceKey == DefaultKey.Value) &&
1046+
if ((serviceKey == null | serviceKey == DefaultKey.Value) &&
10471047
serviceType.IsAssignableVariantGenericTypeFrom(e.Key) &&
10481048
request.MatchFactoryConditionAndMetadata(details, f))
10491049
{
@@ -1054,7 +1054,7 @@ public Factory GetServiceFactoryOrDefault(Request request)
10541054
else
10551055
{
10561056
foreach (var kf in ((FactoriesEntry)e.Value).Factories)
1057-
if (kf.Key.Equals(serviceKey) && // todo: @wip take AnyKey into account?
1057+
if (serviceKey.MatchToNotNullRegisteredKey(kf.Key) &&
10581058
serviceType.IsAssignableVariantGenericTypeFrom(e.Key) &&
10591059
request.MatchFactoryConditionAndMetadata(details, kf.Value))
10601060
{
@@ -1114,8 +1114,8 @@ public Factory GetServiceFactoryOrDefault(Request request)
11141114
}
11151115
}
11161116
else // serviceKey == null
1117-
{
1118-
factories = factories.Match(details, request,
1117+
{
1118+
factories = factories.Match(details, request,
11191119
static (d, r, f) => (f.Key is DefaultKey | f.Key is DefaultDynamicKey) &&
11201120
r.MatchFactoryConditionAndMetadata(d, f.Value));
11211121
}
@@ -1313,7 +1313,9 @@ private static KV<object, Factory> FindFactoryWithTheMinReuseLifespanOrDefault(K
13131313
var reuse = factory.Value.Reuse;
13141314
var lifespan = reuse == null | reuse == Reuse.Transient ? int.MaxValue : reuse.Lifespan;
13151315
if (lifespan == minLifespan)
1316+
{
13161317
multipleFactories = true;
1318+
}
13171319
else if (lifespan < minLifespan)
13181320
{
13191321
minLifespan = lifespan;
@@ -1322,7 +1324,7 @@ private static KV<object, Factory> FindFactoryWithTheMinReuseLifespanOrDefault(K
13221324
}
13231325
}
13241326

1325-
return !multipleFactories && minLifespanFactory != null ? minLifespanFactory : null;
1327+
return !multipleFactories & minLifespanFactory != null ? minLifespanFactory : null;
13261328
}
13271329

13281330
/// <inheritdoc />
@@ -5416,7 +5418,8 @@ private static Expression GetArrayExpression(Request request, Type collectionTyp
54165418
if (requiredItemType.IsGenericType && rules.VariantGenericTypesInResolvedCollection)
54175419
{
54185420
var variantGenericItems = container.GetServiceRegistrations(requiredItemType,
5419-
static (ref Type reqItType, ref ServiceRegistrationInfo src, out ServiceRegistrationInfo res) => {
5421+
static (ref Type reqItType, ref ServiceRegistrationInfo src, out ServiceRegistrationInfo res) =>
5422+
{
54205423
res = src;
54215424
if (!reqItType.IsAssignableVariantGenericTypeFrom(src.ServiceType))
54225425
return false;
@@ -8173,13 +8176,13 @@ public static class Arg
81738176
/// <summary>Contains <see cref="IRegistrator"/> extension methods to simplify general use cases.</summary>
81748177
public static class Registrator
81758178
{
8176-
// todo: @wip We are not supporting the AnyKey outside of the MS.DI, so that's confusing
81778179
// todo: @feature We may need the paired DefaultKey to request/filter collections by default key, because AnyKey does not include DefaultKey
81788180
/// <summary>When registered with it, the the service can be resolved any service key provided</summary>
81798181
public static readonly object AnyKey = new AnyServiceKey();
81808182
/// <summary>Wrap the resolution key</summary>
81818183
public static object AnyKeyOfResolutionKey(object key) => new AnyServiceKey(key);
81828184

8185+
// todo: @wip add IConvertibleToExpression impl
81838186
/// <summary>Represents the **registered** key that you may resolve with any resolution service key</summary>
81848187
public class AnyServiceKey
81858188
{
@@ -10190,6 +10193,7 @@ public static bool MatchFactoryConditionAndMetadata(this Request request, Servic
1019010193
public static bool MatchFactoryReuse(this Request r, Factory f) => f.Reuse?.CanApply(r) ?? true;
1019110194

1019210195
/// <summary>Matching things</summary>
10196+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1019310197
public static bool MatchGeneratedFactory(this Request r, Factory f) =>
1019410198
f.GeneratedFactories == null || f.GetGeneratedFactoryOrDefault(r, ifErrorReturnDefault: true) != null;
1019510199

@@ -13333,7 +13337,7 @@ private static FactoryMethod GetClosedFactoryMethodOrDefault(
1333313337
if (factoryMethodBase != null)
1333413338
{
1333513339
var targetMethods = closedFactoryImplType.GetMethods()
13336-
.Match(factoryMember, factoryMethodBase.GetParameters(),
13340+
.Match(factoryMember, factoryMethodBase.GetParameters(),
1333713341
static (fm, fp, m) => m.Name == fm.Name && m.GetParameters().Length == fp.Length);
1333813342

1333913343
if (targetMethods.Length == 1)
@@ -15904,7 +15908,7 @@ public static bool HasConversionOperatorTo(this Type sourceType, Type targetType
1590415908

1590515909
/// <summary>Finds the conversion operator or returns null</summary>
1590615910
[MethodImpl((MethodImplOptions)256)]
15907-
public static MethodInfo GetConversionOperatorOrNull(this Type sourceType, Type targetType) =>
15911+
public static MethodInfo GetConversionOperatorOrNull(this Type sourceType, Type targetType) =>
1590815912
sourceType.FindConvertOperator(sourceType, targetType) ??
1590915913
targetType.FindConvertOperator(sourceType, targetType);
1591015914

src/DryIoc/ImTools.cs

+117-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ namespace DryIoc.ImTools
3737
using System.Runtime.CompilerServices; // For [MethodImpl(AggressiveInlining)]
3838

3939
/// <summary>General purpose Match operator</summary>
40-
public delegate bool Match<S, T, R>(ref S state, ref T it, out R result);
40+
public delegate bool MatchOp<S, T, R>(ref S state, ref T it, out R result);
41+
42+
/// <summary>General purpose Is operator</summary>
43+
public delegate bool IsOp<S, T>(ref S state, ref T it);
44+
45+
/// <summary>General purpose Map operator</summary>
46+
public delegate R MapOp<S, T, R>(ref S state, ref T it);
4147

4248
/// <summary>Helpers for functional composition</summary>
4349
public static class Fun
@@ -662,6 +668,17 @@ private static R[] Copy<S, T, R>(this T[] source, S state, int sourcePos, int co
662668
return results;
663669
}
664670

671+
private static R[] Copy<S, T, R>(this T[] source, ref S state, int sourcePos, int count, MapOp<S, T, R> map)
672+
{
673+
var results = new R[count];
674+
if (count == 1)
675+
results[0] = map(ref state, ref source[sourcePos]);
676+
else
677+
for (int i = 0, j = sourcePos; i < count; ++i, ++j)
678+
results[i] = map(ref state, ref source[j]);
679+
return results;
680+
}
681+
665682
private static R[] Copy<A, B, T, R>(this T[] source, A a, B b, int sourcePos, int count, Func<A, B, T, R> map)
666683
{
667684
var results = new R[count];
@@ -709,6 +726,18 @@ private static R[] AppendTo<S, T, R>(this T[] source, S state, R[] results, int
709726
return results;
710727
}
711728

729+
private static R[] AppendTo<S, T, R>(this T[] source, ref S state, R[] results, int sourcePos, int count, MapOp<S, T, R> map)
730+
{
731+
var oldResultsCount = results.Length;
732+
Array.Resize(ref results, oldResultsCount + count);
733+
if (count == 1)
734+
results[oldResultsCount] = map(ref state, ref source[sourcePos]);
735+
else
736+
for (int i = oldResultsCount, j = sourcePos; i < results.Length; ++i, ++j)
737+
results[i] = map(ref state, ref source[j]);
738+
return results;
739+
}
740+
712741
private static R[] AppendTo<A, B, T, R>(this T[] source, A a, B b, R[] results, int sourcePos, int count, Func<A, B, T, R> map)
713742
{
714743
var oldResultsCount = results.Length;
@@ -993,6 +1022,71 @@ public static R[] Match<S, T, R>(this T[] source, S state, Func<S, T, bool> cond
9931022
return matches ?? (matchStart == 0 ? source.Copy(state, 0, source.Length, map) : Empty<R>());
9941023
}
9951024

1025+
/// <summary>Match with the additional state to use in <paramref name="condition"/> and <paramref name="map"/>
1026+
/// to minimize the allocations in <paramref name="condition"/> lambda closure </summary>
1027+
public static R[] Match<S, T, R>(this T[] source, ref S state, IsOp<S, T> condition, MapOp<S, T, R> map)
1028+
{
1029+
if (source == null)
1030+
return null;
1031+
if (source.Length == 0)
1032+
return Empty<R>();
1033+
1034+
if (source.Length == 1)
1035+
{
1036+
var item = source[0];
1037+
return condition(ref state, ref item) ? new[] { map(ref state, ref item) } : Empty<R>();
1038+
}
1039+
1040+
if (source.Length == 2)
1041+
{
1042+
var c0 = condition(ref state, ref source[0]);
1043+
var c1 = condition(ref state, ref source[1]);
1044+
return c0 & c1 ? new[] { map(ref state, ref source[0]), map(ref state, ref source[1]) }
1045+
: c0 ? new[] { map(ref state, ref source[0]) }
1046+
: c1 ? new[] { map(ref state, ref source[1]) }
1047+
: Empty<R>();
1048+
}
1049+
1050+
if (source.Length == 3)
1051+
{
1052+
var c0 = condition(ref state, ref source[0]);
1053+
var c1 = condition(ref state, ref source[1]);
1054+
var c2 = condition(ref state, ref source[2]);
1055+
return c0 & c1 & c2 ? new[] { map(ref state, ref source[0]), map(ref state, ref source[1]), map(ref state, ref source[2]) }
1056+
: c0 ? (c1 ? new[] { map(ref state, ref source[0]), map(ref state, ref source[1]) }
1057+
: c2 ? new[] { map(ref state, ref source[0]), map(ref state, ref source[2]) }
1058+
: new[] { map(ref state, ref source[0]) })
1059+
: c1 ? (c2 ? new[] { map(ref state, ref source[1]), map(ref state, ref source[2]) }
1060+
: new[] { map(ref state, ref source[1]) })
1061+
: c2 ? new[] { map(ref state, ref source[2]) }
1062+
: Empty<R>();
1063+
}
1064+
1065+
var matchStart = 0;
1066+
R[] matches = null;
1067+
var matchFound = false;
1068+
1069+
var i = 0;
1070+
for (; i < source.Length; ++i)
1071+
if (!(matchFound = condition(ref state, ref source[i])))
1072+
{
1073+
// for accumulated matched items
1074+
if (i != 0 && i > matchStart)
1075+
matches = matches == null
1076+
? source.Copy(ref state, matchStart, i - matchStart, map)
1077+
: source.AppendTo(ref state, matches, matchStart, i - matchStart, map);
1078+
matchStart = i + 1; // guess the next match start will be after the non-matched item
1079+
}
1080+
1081+
// when last match was found but not all items are matched (hence matchStart != 0)
1082+
if (matchFound && matchStart != 0)
1083+
return matches == null
1084+
? source.Copy(ref state, matchStart, i - matchStart, map)
1085+
: source.AppendTo(ref state, matches, matchStart, i - matchStart, map);
1086+
1087+
return matches ?? (matchStart == 0 ? source.Copy(ref state, 0, source.Length, map) : Empty<R>());
1088+
}
1089+
9961090
/// <summary>Match with the additional state to use in <paramref name="condition"/> and <paramref name="map"/>
9971091
/// to minimize the allocations in <paramref name="condition"/> lambda closure </summary>
9981092
public static R[] Match<A, B, T, R>(this T[] source, A a, B b, Func<A, B, T, bool> condition, Func<A, B, T, R> map)
@@ -1091,6 +1185,28 @@ public static R[] Map<T, S, R>(this T[] source, S state, Func<S, T, R> map)
10911185
return results;
10921186
}
10931187

1188+
/// Map with additional state to use in <paramref name="map"/> to minimize allocations in <paramref name="map"/> lambda closure
1189+
public static R[] Map<T, S, R>(this T[] source, ref S state, MapOp<S, T, R> map)
1190+
{
1191+
if (source == null)
1192+
return null;
1193+
1194+
var sourceCount = source.Length;
1195+
if (sourceCount == 0)
1196+
return Empty<R>();
1197+
1198+
if (sourceCount == 1)
1199+
return new[] { map(ref state, ref source[0]) };
1200+
1201+
if (sourceCount == 2)
1202+
return new[] { map(ref state, ref source[0]), map(ref state, ref source[1]) };
1203+
1204+
var results = new R[sourceCount];
1205+
for (var i = 0; i < source.Length; i++)
1206+
results[i] = map(ref state, ref source[i]);
1207+
return results;
1208+
}
1209+
10941210
/// Map with additional two states to use in <paramref name="map"/> to minimize allocations in <paramref name="map"/> lambda closure
10951211
public static R[] Map<T, A, B, R>(this T[] source, A a, B b, Func<A, B, T, R> map)
10961212
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using NUnit.Framework;
2+
3+
namespace DryIoc.IssuesTests;
4+
5+
[TestFixture]
6+
public sealed class GHIssue631_Conditional_registrations : ITest
7+
{
8+
public int Run()
9+
{
10+
// TestWithDefaultScopeToSingleton(); // todo: @fixme @wip the test
11+
TestWithIdenticalScopes();
12+
return 2;
13+
}
14+
15+
[Test]
16+
public void TestWithDefaultScopeToSingleton()
17+
{
18+
var container = new Container();
19+
20+
container.Register<IService, MyServiceA>(
21+
reuse: Reuse.Transient,
22+
setup: Setup.With(condition: request => request.DirectParent.ServiceType == typeof(ServiceConsumerA))
23+
);
24+
25+
// the intention is having a default resolution if previous conditions aren't satisfied !!
26+
container.Register<IService, MyServiceB>(
27+
reuse: Reuse.Singleton
28+
);
29+
30+
container.Register<ServiceConsumerA>();
31+
container.Register<ServiceConsumerB>();
32+
33+
var consumerA = container.Resolve<ServiceConsumerA>();
34+
var consumerB = container.Resolve<ServiceConsumerB>();
35+
36+
Assert.NotNull(consumerA);
37+
Assert.NotNull(consumerB);
38+
39+
// here is the mismatch from consumerA.Service type !!.
40+
Assert.IsInstanceOf<MyServiceA>(consumerA.Service);
41+
Assert.IsInstanceOf<MyServiceB>(consumerB.Service);
42+
}
43+
44+
[Test]
45+
public void TestWithIdenticalScopes()
46+
{
47+
var container = new Container();
48+
49+
container.Register<IService, MyServiceA>(
50+
reuse: Reuse.Transient,
51+
setup: Setup.With(condition: request => request.DirectParent.ServiceType == typeof(ServiceConsumerA))
52+
);
53+
54+
// the intention is having a default resolution if previous conditions aren't satisfied !!
55+
container.Register<IService, MyServiceB>(
56+
reuse: Reuse.Transient
57+
);
58+
59+
container.Register<ServiceConsumerA>();
60+
container.Register<ServiceConsumerB>();
61+
62+
var consumerA = container.Resolve<ServiceConsumerA>();
63+
var consumerB = container.Resolve<ServiceConsumerB>();
64+
65+
Assert.NotNull(consumerA);
66+
Assert.NotNull(consumerB);
67+
68+
// those asserts work, so types match !
69+
Assert.IsInstanceOf<MyServiceA>(consumerA.Service);
70+
Assert.IsInstanceOf<MyServiceB>(consumerB.Service);
71+
}
72+
73+
public class ServiceConsumerA
74+
{
75+
public IService Service { get; private set; }
76+
public ServiceConsumerA(IService service) => Service = service;
77+
}
78+
79+
public class ServiceConsumerB
80+
{
81+
public IService Service { get; private set; }
82+
public ServiceConsumerB(IService service) => Service = service;
83+
}
84+
85+
public class MyServiceA : IService { }
86+
87+
public class MyServiceB : IService { }
88+
89+
public interface IService { }
90+
}

test/DryIoc.TestRunner/Program.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ public class Program
99
{
1010
public static void Main()
1111
{
12-
new GHIssue228_Updated_DryIoc_from_4_to_4_1_in_Unity_Engine_project_keyed_register_resolve_wont_work_anymore().Run();
12+
new GHIssue631_Conditional_registrations().Run();
1313

1414
RunAllTests();
1515

16+
// new GHIssue228_Updated_DryIoc_from_4_to_4_1_in_Unity_Engine_project_keyed_register_resolve_wont_work_anymore().Run();
1617
// new Issue397_ActionExportsTypeConversion().Run();
1718
// new GHIssue580_Scope_is_lost_in_IResolver_inside_scope_because_of_singleton().Run();
1819
// new GHIssue574_Cannot_register_multiple_impls_in_child_container_with_default_service_key().Run();

0 commit comments

Comments
 (0)