~ruther/NosSmooth.Local

ref: d5b3c3ffb40aa86f582a0f26fc0376caa245f220 NosSmooth.Local/src/Core/NosSmooth.LocalBinding/Optional.cs -rw-r--r-- 6.8 KiB
d5b3c3ff — Rutherther Merge pull request #18 from Rutherther/feat/optional-binding 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//
//  Optional.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;
using NosSmooth.LocalBinding.Errors;
using Remora.Results;

namespace NosSmooth.LocalBinding;

/// <summary>
/// An optional, used mainly for hooks and binding modules,
/// to make it possible to check whether a module is loaded
/// in runtime.
/// </summary>
/// <typeparam name="T">The type of underlying value.</typeparam>
public class Optional<T>
    where T : notnull
{
    /// <summary>
    /// An empty optional (no value present).
    /// </summary>
    public static readonly Optional<T> Empty = new();

    /// <summary>
    /// Initializes a new instance of the <see cref="Optional{T}"/> class.
    /// </summary>
    /// <param name="value">The underlying value of the optional. Not present when null.</param>
    public Optional(T? value = default)
    {
        Value = value;
    }

    /// <summary>
    /// Gets whether the value is present.
    /// </summary>
    [MemberNotNullWhen(true, "Value")]
    public bool IsPresent => Value is not null;

    /// <summary>
    /// Gets the underlying value.
    /// </summary>
    public T? Value { get; }

    /// <summary>
    /// Tries to get the underlying value, if it's present.
    /// If it's not present, value won't be set.
    /// </summary>
    /// <param name="value">The underlying value.</param>
    /// <returns>Whether the value is present.</returns>
    public bool TryGet([NotNullWhen(true)] out T? value)
    {
        value = Value;
        return IsPresent;
    }

    /// <summary>
    /// Tries to execute an action on the value, if it exists.
    /// </summary>
    /// <remarks>
    /// Does nothing, if the value does not exist.
    /// </remarks>
    /// <param name="action">The action to execute.</param>
    /// <returns>Whether the value is present.</returns>
    public bool TryDo(Action<T> action)
    {
        if (IsPresent)
        {
            action(Value);
        }

        return IsPresent;
    }

    /// <summary>
    /// Gets something from the underlying value,
    /// exposing it as optional that is empty,
    /// in case this optional is empty as well.
    /// </summary>
    /// <param name="get">The function to obtain something from the value with.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>An optional, present if this optional's value is present.</returns>
    public Optional<TU> Map<TU>(Func<T, TU> get)
        where TU : notnull
    {
        if (IsPresent)
        {
            return get(Value);
        }

        return Optional<TU>.Empty;
    }

    /// <summary>
    /// Gets something from the underlying value like <see cref="Map{TU}"/>.
    /// </summary>
    /// <remarks>
    /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
    /// instead of returning an optional.
    /// </remarks>
    /// <param name="get">The function to obtain something from the value with.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>A result, successful in case this optional's value is present.</returns>
    public Result<TU> MapResult<TU>(Func<T, TU> get)
        where TU : notnull
    {
        if (IsPresent)
        {
            return OptionalUtilities.TryGet(() => get(Value));
        }

        return new OptionalNotPresentError(nameof(T));
    }

    /// <summary>
    /// Does something on the underlying value like <see cref="TryDo"/>, but returns a result
    /// in case the value is not present.
    /// </summary>
    /// <param name="get">The function to execute on the value.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>A result, successful in case this optional's value is present.</returns>
    public Result MapResult(Action<T> get)
    {
        if (IsPresent)
        {
            return OptionalUtilities.Try(() => get(Value));
        }

        return new OptionalNotPresentError(nameof(T));
    }

    /// <summary>
    /// Gets something from the underlying value like <see cref="Map{TU}"/>.
    /// </summary>
    /// <remarks>
    /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
    /// instead of returning an optional.
    ///
    /// The get function returns a result that will be returned if this optional is present.
    /// </remarks>
    /// <param name="get">The function to obtain something from the value with.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>A result from the function, <see cref="OptionalNotPresentError"/> in case this optional is not present.</returns>
    public Result<TU> MapResult<TU>(Func<T, Result<TU>> get)
        where TU : notnull
    {
        if (IsPresent)
        {
            return get(Value);
        }

        return new OptionalNotPresentError(nameof(T));
    }

    /// <summary>
    /// Does something on the underlying value like <see cref="TryDo"/>, but returns a result
    /// in case the value is not present.
    /// </summary>
    /// <remarks>
    /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
    /// instead of returning an optional.
    ///
    /// The get function returns a result that will be returned if this optional is present.
    /// </remarks>
    /// <param name="get">The function to obtain something from the value with.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>A result from the function, <see cref="OptionalNotPresentError"/> in case this optional is not present.</returns>
    public Result MapResult(Func<T, Result> get)
    {
        if (IsPresent)
        {
            return get(Value);
        }

        return new OptionalNotPresentError(nameof(T));
    }

    /// <summary>
    /// Forcefully gets the underlying value.
    /// If it's not present, <see cref="InvalidOperationException"/> will be thrown.
    /// </summary>
    /// <remarks>
    /// Try to use other methods that return results where possible as they are easier to handle.
    /// </remarks>
    /// <returns>The underlying value.</returns>
    /// <exception cref="InvalidOperationException">Thrown in case the value is not present.</exception>
    public T Get()
    {
        if (!IsPresent)
        {
            throw new InvalidOperationException
            (
                $"Could not get {nameof(T)}. Did you forget to call initialization or was there an error?"
            );
        }

        return Value;
    }

    /// <summary>
    /// Cast a value to optional.
    /// </summary>
    /// <param name="val">The value to cast.</param>
    /// <returns>The created optional.</returns>
    public static implicit operator Optional<T>(T val)
    {
        return new Optional<T>(val);
    }
}
Do not follow this link