Skip to content

Commit 66825f9

Browse files
committed
Reintroduced simplified Ioc class
1 parent 2ceb110 commit 66825f9

File tree

1 file changed

+111
-0
lines changed
  • Microsoft.Toolkit.Mvvm/DependencyInjection

1 file changed

+111
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Threading;
7+
8+
#nullable enable
9+
10+
namespace Microsoft.Toolkit.Mvvm.DependencyInjection
11+
{
12+
/// <summary>
13+
/// A type that facilitates the use of the <see cref="IServiceProvider"/> type.
14+
/// The <see cref="Ioc"/> provides the ability to configure services in a singleton, thread-safe
15+
/// service provider instance, which can then be used to resolve service instances.
16+
/// The first step to use this feature is to declare some services, for instance:
17+
/// <code>
18+
/// public interface ILogger
19+
/// {
20+
/// void Log(string text);
21+
/// }
22+
/// </code>
23+
/// <code>
24+
/// public class ConsoleLogger : ILogger
25+
/// {
26+
/// void Log(string text) => Console.WriteLine(text);
27+
/// }
28+
/// </code>
29+
/// Then the services configuration should then be done at startup, by calling the <see cref="ConfigureServices"/>
30+
/// method and passing an <see cref="IServiceProvider"/> instance with the services to use. That instance can
31+
/// be from any library offering dependency injection functionality, such as Microsoft.Extensions.DependencyInjection.
32+
/// For instance, using that library, <see cref="ConfigureServices"/> can be used as follows in this example:
33+
/// <code>
34+
/// Ioc.Default.ConfigureServices(
35+
/// new ServiceCollection()
36+
/// .AddSingleton&lt;ILogger, Logger&gt;()
37+
/// .BuildServiceProvider());
38+
/// </code>
39+
/// Finally, you can use the <see cref="Ioc"/> instance (which implements <see cref="IServiceProvider"/>)
40+
/// to retrieve the service instances from anywhere in your application, by doing as follows:
41+
/// <code>
42+
/// Ioc.Default.GetService&lt;ILogger&gt;().Log("Hello world!");
43+
/// </code>
44+
/// </summary>
45+
public sealed class Ioc : IServiceProvider
46+
{
47+
/// <summary>
48+
/// Gets the default <see cref="Ioc"/> instance.
49+
/// </summary>
50+
public static Ioc Default { get; } = new Ioc();
51+
52+
/// <summary>
53+
/// The <see cref="IServiceProvider"/> instance to use, if initialized.
54+
/// </summary>
55+
private volatile IServiceProvider? serviceProvider;
56+
57+
/// <inheritdoc/>
58+
public object? GetService(Type serviceType)
59+
{
60+
// As per section I.12.6.6 of the official CLI ECMA-335 spec:
61+
// "[...] read and write access to properly aligned memory locations no larger than the native
62+
// word size is atomic when all the write accesses to a location are the same size. Atomic writes
63+
// shall alter no bits other than those written. Unless explicit layout control is used [...],
64+
// data elements no larger than the natural word size [...] shall be properly aligned.
65+
// Object references shall be treated as though they are stored in the native word size."
66+
// The field being accessed here is of native int size (reference type), and is only ever accessed
67+
// directly and atomically by a compare exchange instruction (see below), or here. We can therefore
68+
// assume this read is thread safe with respect to accesses to this property or to invocations to one
69+
// of the available configuration methods. So we can just read the field directly and make the necessary
70+
// check with our local copy, without the need of paying the locking overhead from this get accessor.
71+
IServiceProvider? provider = this.serviceProvider;
72+
73+
if (provider is null)
74+
{
75+
ThrowInvalidOperationExceptionForMissingInitialization();
76+
}
77+
78+
return provider!.GetService(serviceType);
79+
}
80+
81+
/// <summary>
82+
/// Initializes the shared <see cref="IServiceProvider"/> instance.
83+
/// </summary>
84+
/// <param name="serviceProvider">The input <see cref="IServiceProvider"/> instance to use.</param>
85+
public void ConfigureServices(IServiceProvider serviceProvider)
86+
{
87+
IServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, serviceProvider, null);
88+
89+
if (!(oldServices is null))
90+
{
91+
ThrowInvalidOperationExceptionForRepeatedConfiguration();
92+
}
93+
}
94+
95+
/// <summary>
96+
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="IServiceProvider"/> property is used before initialization.
97+
/// </summary>
98+
private static void ThrowInvalidOperationExceptionForMissingInitialization()
99+
{
100+
throw new InvalidOperationException("The service provider has not been configured yet");
101+
}
102+
103+
/// <summary>
104+
/// Throws an <see cref="InvalidOperationException"/> when a configuration is attempted more than once.
105+
/// </summary>
106+
private static void ThrowInvalidOperationExceptionForRepeatedConfiguration()
107+
{
108+
throw new InvalidOperationException("The default service provider has already been configured");
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)