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<ILogger, Logger>()
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<ILogger>().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