Skip to content

Commit d546418

Browse files
committed
Added support of functional interfaces
1 parent 8ad51de commit d546418

File tree

2 files changed

+133
-12
lines changed

2 files changed

+133
-12
lines changed

src/DotNext.Tests/Runtime/ValueReferenceTests.cs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public sealed class ValueReferenceTests : Test
88
[Fact]
99
public static void MutableFieldRef()
1010
{
11-
var obj = new MyClass() { AnotherField = string.Empty };
11+
var obj = new MyClass { AnotherField = string.Empty };
1212
var reference = new ValueReference<int>(obj, ref obj.Field);
1313

1414
obj.Field = 20;
@@ -22,7 +22,7 @@ public static void MutableFieldRef()
2222
[Fact]
2323
public static void ImmutableFieldRef()
2424
{
25-
var obj = new MyClass() { AnotherField = string.Empty };
25+
var obj = new MyClass { AnotherField = string.Empty };
2626
var reference = new ReadOnlyValueReference<int>(obj, in obj.Field);
2727

2828
obj.Field = 20;
@@ -35,7 +35,7 @@ public static void ImmutableFieldRef()
3535
[Fact]
3636
public static void MutableToImmutableRef()
3737
{
38-
var obj = new MyClass() { AnotherField = string.Empty };
38+
var obj = new MyClass { AnotherField = string.Empty };
3939
var reference = new ValueReference<int>(obj, ref obj.Field);
4040
ReadOnlyValueReference<int> roReference = reference;
4141

@@ -49,7 +49,7 @@ public static void MutableToImmutableRef()
4949
[Fact]
5050
public static void MutableRefEquality()
5151
{
52-
var obj = new MyClass() { AnotherField = string.Empty };
52+
var obj = new MyClass { AnotherField = string.Empty };
5353
var reference1 = new ValueReference<int>(obj, ref obj.Field);
5454
var reference2 = new ValueReference<int>(obj, ref obj.Field);
5555

@@ -59,7 +59,7 @@ public static void MutableRefEquality()
5959
[Fact]
6060
public static void ImmutableRefEquality()
6161
{
62-
var obj = new MyClass() { AnotherField = string.Empty };
62+
var obj = new MyClass { AnotherField = string.Empty };
6363
var reference1 = new ReadOnlyValueReference<int>(obj, in obj.Field);
6464
var reference2 = new ReadOnlyValueReference<int>(obj, in obj.Field);
6565

@@ -82,23 +82,28 @@ public static void ReferenceToArray()
8282
[Fact]
8383
public static void MutableEmptyRef()
8484
{
85-
var reference = default(ValueReference<float>);
85+
var reference = default(ValueReference<string>);
8686
True(reference.IsEmpty);
8787
Null(reference.ToString());
8888

89-
Span<float> span = reference;
89+
Span<string> span = reference;
9090
True(span.IsEmpty);
91+
92+
Throws<NullReferenceException>((Func<string>)reference);
93+
Throws<NullReferenceException>(((Action<string>)reference).Bind(string.Empty));
9194
}
9295

9396
[Fact]
9497
public static void ImmutableEmptyRef()
9598
{
96-
var reference = default(ReadOnlyValueReference<float>);
99+
var reference = default(ReadOnlyValueReference<string>);
97100
True(reference.IsEmpty);
98101
Null(reference.ToString());
99102

100-
ReadOnlySpan<float> span = reference;
103+
ReadOnlySpan<string> span = reference;
101104
True(span.IsEmpty);
105+
106+
Throws<NullReferenceException>((Func<string>)reference);
102107
}
103108

104109
[Fact]
@@ -107,10 +112,18 @@ public static void AnonymousValue()
107112
var reference = new ValueReference<int>(42);
108113
Equal(42, reference.Value);
109114

115+
((Action<int>)reference).Invoke(52);
116+
Equal(52, ToFunc<ValueReference<int>, int>(reference).Invoke());
117+
110118
ReadOnlyValueReference<int> roRef = reference;
111-
Equal(42, roRef.Value);
119+
Equal(52, roRef.Value);
120+
Equal(52, ToFunc<ReadOnlyValueReference<int>, int>(reference).Invoke());
112121
}
113122

123+
private static Func<T> ToFunc<TSupplier, T>(TSupplier supplier)
124+
where TSupplier : ISupplier<T>
125+
=> supplier.ToDelegate();
126+
114127
[Fact]
115128
public static void StaticObjectAccess()
116129
{
@@ -124,6 +137,7 @@ public static void StaticObjectAccess()
124137

125138
True(reference == new ValueReference<string>(ref MyClass.StaticObject));
126139
Same(MyClass.StaticObject, reference.Value);
140+
Same(MyClass.StaticObject, ToFunc<ValueReference<string>, string>(reference).Invoke());
127141
}
128142

129143
[Fact]
@@ -193,6 +207,8 @@ public static void ReadOnlySpanInterop()
193207
True(Unsafe.AreSame(in reference.Value, in span[0]));
194208
}
195209

210+
211+
196212
private record class MyClass : IResettable
197213
{
198214
internal static string StaticObject;

src/DotNext/Runtime/ValueReference.cs

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
namespace DotNext.Runtime;
99

10+
using Runtime.CompilerServices;
11+
1012
/// <summary>
1113
/// Represents a mutable reference to the field.
1214
/// </summary>
@@ -17,7 +19,9 @@ namespace DotNext.Runtime;
1719
[EditorBrowsable(EditorBrowsableState.Advanced)]
1820
public readonly struct ValueReference<T>(object owner, ref T fieldRef) :
1921
IEquatable<ValueReference<T>>,
20-
IEqualityOperators<ValueReference<T>, ValueReference<T>, bool>
22+
IEqualityOperators<ValueReference<T>, ValueReference<T>, bool>,
23+
ISupplier<T>,
24+
IConsumer<T>
2125
{
2226
private readonly nint offset = RawData.GetOffset(owner, in fieldRef);
2327

@@ -76,8 +80,47 @@ public ValueReference(ref T staticFieldRef)
7680
/// </summary>
7781
public ref T Value => ref RawData.GetObjectData<T>(owner, offset);
7882

83+
/// <inheritdoc cref="IConsumer{T}.Invoke(T)"/>
84+
void IConsumer<T>.Invoke(T value) => Value = value;
85+
86+
/// <inheritdoc cref="IFunctional{T}.ToDelegate()"/>
87+
Action<T> IFunctional<Action<T>>.ToDelegate() => ToAction();
88+
89+
/// <inheritdoc cref="ISupplier{T}.Invoke()"/>
90+
T ISupplier<T>.Invoke() => Value;
91+
92+
/// <inheritdoc cref="IFunctional{T}.ToDelegate()"/>
93+
Func<T> IFunctional<Func<T>>.ToDelegate() => ToFunc();
94+
7995
private bool SameObject(object? other) => ReferenceEquals(owner, other);
8096

97+
private Func<T> ToFunc()
98+
=> Intrinsics.ChangeType<ValueReference<T>, ReadOnlyValueReference<T>>(in this).ToFunc();
99+
100+
private Action<T> ToAction()
101+
{
102+
Action<T> result;
103+
104+
if (IsEmpty)
105+
{
106+
result = ThrowNullReferenceException;
107+
}
108+
else if (ReferenceEquals(owner, Sentinel.Instance))
109+
{
110+
result = new StaticFieldAccessor<T>(offset).SetValue;
111+
}
112+
else
113+
{
114+
IConsumer<T> consumer = this;
115+
result = consumer.Invoke;
116+
}
117+
118+
return result;
119+
120+
[DoesNotReturn]
121+
static void ThrowNullReferenceException(T value) => throw new NullReferenceException();
122+
}
123+
81124
/// <inheritdoc/>
82125
public override string? ToString()
83126
=> owner is not null ? RawData.GetObjectData<T>(owner, offset)?.ToString() : null;
@@ -126,6 +169,22 @@ public static implicit operator ReadOnlyValueReference<T>(ValueReference<T> refe
126169
/// <returns>The span that contains <see cref="Value"/>; or empty span if <paramref name="reference"/> is empty.</returns>
127170
public static implicit operator Span<T>(ValueReference<T> reference)
128171
=> reference.IsEmpty ? new() : new(ref reference.Value);
172+
173+
/// <summary>
174+
/// Returns a setter for the memory location.
175+
/// </summary>
176+
/// <param name="reference">A reference to a value.</param>
177+
/// <returns>A setter for the memory location.</returns>
178+
public static explicit operator Action<T>(ValueReference<T> reference)
179+
=> reference.ToAction();
180+
181+
/// <summary>
182+
/// Returns a getter for the memory location.
183+
/// </summary>
184+
/// <param name="reference">A reference to a value.</param>
185+
/// <returns>A getter for the memory location.</returns>
186+
public static explicit operator Func<T>(ValueReference<T> reference)
187+
=> reference.ToFunc();
129188
}
130189

131190
/// <summary>
@@ -138,7 +197,8 @@ public static implicit operator Span<T>(ValueReference<T> reference)
138197
[EditorBrowsable(EditorBrowsableState.Advanced)]
139198
public readonly struct ReadOnlyValueReference<T>(object owner, ref readonly T fieldRef) :
140199
IEquatable<ReadOnlyValueReference<T>>,
141-
IEqualityOperators<ReadOnlyValueReference<T>, ReadOnlyValueReference<T>, bool>
200+
IEqualityOperators<ReadOnlyValueReference<T>, ReadOnlyValueReference<T>, bool>,
201+
ISupplier<T>
142202
{
143203
private readonly nint offset = RawData.GetOffset(owner, in fieldRef);
144204

@@ -179,6 +239,36 @@ public ReadOnlyValueReference(ref readonly T staticFieldRef)
179239
/// </summary>
180240
public ref readonly T Value => ref RawData.GetObjectData<T>(owner, offset);
181241

242+
/// <inheritdoc cref="ISupplier{T}.Invoke()"/>
243+
T ISupplier<T>.Invoke() => Value;
244+
245+
/// <inheritdoc cref="IFunctional{T}.ToDelegate()"/>
246+
Func<T> IFunctional<Func<T>>.ToDelegate() => ToFunc();
247+
248+
internal Func<T> ToFunc()
249+
{
250+
Func<T> result;
251+
if (IsEmpty)
252+
{
253+
result = ThrowNullReferenceException;
254+
}
255+
else if (ReferenceEquals(owner, Sentinel.Instance))
256+
{
257+
result = new StaticFieldAccessor<T>(offset).GetValue;
258+
}
259+
else
260+
{
261+
ISupplier<T> supplier = this;
262+
result = supplier.Invoke;
263+
}
264+
265+
return result;
266+
267+
[DoesNotReturn]
268+
static T ThrowNullReferenceException()
269+
=> throw new NullReferenceException();
270+
}
271+
182272
private bool SameObject(object? other) => ReferenceEquals(owner, other);
183273

184274
/// <inheritdoc/>
@@ -221,6 +311,14 @@ public bool Equals(ReadOnlyValueReference<T> reference)
221311
/// <returns>The span that contains <see cref="Value"/>; or empty span if <paramref name="reference"/> is empty.</returns>
222312
public static implicit operator ReadOnlySpan<T>(ReadOnlyValueReference<T> reference)
223313
=> reference.IsEmpty ? new() : new(in reference.Value);
314+
315+
/// <summary>
316+
/// Returns a getter for the memory location.
317+
/// </summary>
318+
/// <param name="reference">A reference to a value.</param>
319+
/// <returns>A getter for the memory location.</returns>
320+
public static explicit operator Func<T>(ReadOnlyValueReference<T> reference)
321+
=> reference.ToFunc();
224322
}
225323

226324
[SuppressMessage("Performance", "CA1812", Justification = "Used for reinterpret cast")]
@@ -259,4 +357,11 @@ internal static ref T GetObjectData<T>(object owner, nint offset)
259357
ref var rawData = ref Unsafe.As<RawData>(owner).data;
260358
return ref Unsafe.As<byte, T>(ref Unsafe.Add(ref rawData, offset));
261359
}
360+
}
361+
362+
file sealed class StaticFieldAccessor<T>(nint offset)
363+
{
364+
public T GetValue() => RawData.GetObjectData<T>(Sentinel.Instance, offset);
365+
366+
public void SetValue(T value) => RawData.GetObjectData<T>(Sentinel.Instance, offset) = value;
262367
}

0 commit comments

Comments
 (0)