-
Notifications
You must be signed in to change notification settings - Fork 0
/
Of.cs
115 lines (105 loc) · 4.1 KB
/
Of.cs
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
using System;
using System.Text.Json.Serialization;
using Recore.Text.Json.Serialization.Converters;
namespace Recore
{
/// <summary>
/// Abstract base class for defining types that alias an existing type.
/// </summary>
/// <remarks>
/// Use <see cref="Of{T}"/> to create a strongly-typed "alias" of another type.
///
/// You can use a <c>using</c> directive to create an alias for a type,
/// but the scope of that alias is limited to that file.
/// Furthermore, the alias is just that -- an alias -- not a separate type.
/// So, an alias won't prevent errors like this:
/// <code>
/// using Name = string;
/// using Address = string;
/// class Person
/// {
/// public Person(int age, Address address, Name name)
/// {
/// }
/// }
///
/// var person = new Person(22, "Alice", "1 Microsoft Way"); // oops!
/// </code>
///
/// Note: as of C# 9, you can replace many use cases for <see cref="Of{T}"/> with record types:
/// <code>
/// record Address(string Value);
/// record Name(string Value);
/// </code>
///
/// <see cref="Of{T}"/> is not marked with <see cref="ObsoleteAttribute"/> because records have some limitations that classes do not have.
/// Also, <see cref="Of{T}"/> provides easy JSON serialization and implicit conversion to its wrapped type, which records do not provide.
/// </remarks>
[JsonConverter(typeof(OfConverter))]
public abstract class Of<T> : IEquatable<Of<T>?>
{
/// <summary>
/// The underlying instance of the wrapped type.
/// </summary>
public T? Value { get; init; }
/// <summary>
/// Converts this <see cref="Of{T}"/> to another subtype of <see cref="Of{T}"/>
/// with the same value of <typeparamref name="T"/>.
/// </summary>
public TOf To<TOf>() where TOf : Of<T>, new()
=> new TOf { Value = Value };
/// <summary>
/// Returns the string representation for the underlying object.
/// </summary>
#nullable disable // Set to oblivious because T.ToString() is oblivious
public override string ToString()
{
if (Value == null)
{
return string.Empty;
}
else
{
return Value.ToString();
}
}
#nullable enable
/// <summary>
/// Determines whether this instance is equal to another object.
/// </summary>
public override bool Equals(object? obj)
=> obj is Of<T> of && Equals(of);
/// <summary>
/// Determines whether two instances of the type are equal.
/// </summary>
/// <remarks>
/// Note that instances of two separate subtypes of <see cref="Of{T}"/>
/// will compare equal to each other if their values are the same type and are equal.
/// </remarks>
public bool Equals(Of<T>? other)
=> !(other is null)
&& Equals(Value, other.Value);
/// <summary>
/// Returns the hash code for the underlying object.
/// </summary>
public override int GetHashCode() => Value!.GetHashCode();
/// <summary>
/// Determines whether two instances of the type are equal.
/// </summary>
public static bool operator ==(Of<T> lhs, Of<T> rhs)
=> Equals(lhs, rhs);
/// <summary>
/// Determines whether two instances of the type are not equal.
/// </summary>
public static bool operator !=(Of<T> lhs, Of<T> rhs)
=> !Equals(lhs, rhs);
/// <summary>
/// Converts an instance of <see cref="Of{T}"/> to its inner type <typeparamref name="T"/>.
/// </summary>
/// <remarks>
/// <see cref="Of{T}"/> is conceptually (though not in fact) a subtype of <typeparamref name="T"/>.
/// This conversion allows instances of <see cref="Of{T}"/> to work with methods out of the caller's control.
/// </remarks>
public static implicit operator T?(Of<T> of) => of.Value;
}
}