Skip to content

Commit f4efef7

Browse files
committed
TypedRef
1 parent df78e97 commit f4efef7

File tree

9 files changed

+188
-0
lines changed

9 files changed

+188
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
</Project>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace ConsoleApp1;
2+
3+
public interface ITupleAccessor
4+
{
5+
int Length { get; }
6+
TypedRef this[int index] { get; }
7+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using ConsoleApp1;
2+
using System.Runtime.CompilerServices;
3+
4+
M(1, 2);
5+
M("1", 2.0);
6+
M(true, DateTimeOffset.Now);
7+
M((byte)1, (short)2, (ushort?)3);
8+
9+
partial class Program
10+
{
11+
public static void M<T1, T2>(T1 x1, T2 x2)
12+
{
13+
var tuple = (x1, x2);
14+
var accessor = Accessor.Create(ref tuple);
15+
A(tuple); // boxed
16+
B(accessor);
17+
Console.WriteLine();
18+
}
19+
20+
public static void M<T1, T2, T3>(T1 x1, T2 x2, T3 x3)
21+
{
22+
var tuple = (x1, x2, x3);
23+
var accessor = Accessor.Create(ref tuple);
24+
A(tuple); // boxed
25+
B(accessor);
26+
}
27+
28+
static void A(ITuple tuple)
29+
{
30+
for (int i = 0; i < tuple.Length; i++)
31+
{
32+
Console.WriteLine(tuple[i]?.GetType().Name);
33+
}
34+
}
35+
36+
static void B<TAccessor>(TAccessor tuple)
37+
where TAccessor : struct, ITupleAccessor, allows ref struct
38+
{
39+
foreach (var t in tuple)
40+
{
41+
Console.WriteLine(t.Type.Name);
42+
}
43+
}
44+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace ConsoleApp1;
2+
3+
public readonly ref struct TupleAccessor<T1, T2>(ref (T1, T2) tuple) : ITupleAccessor
4+
{
5+
private readonly ref (T1, T2) _tuple = ref tuple;
6+
public readonly int Length => 2;
7+
public TypedRef this[int index] => index switch
8+
{
9+
0 => TypedRef.Create(ref _tuple.Item1),
10+
1 => TypedRef.Create(ref _tuple.Item2),
11+
_ => throw new IndexOutOfRangeException(),
12+
};
13+
}
14+
15+
partial class Accessor
16+
{
17+
public static TupleAccessor<T1, T2> Create<T1, T2>(ref this (T1, T2) tuple) => new(ref tuple);
18+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace ConsoleApp1;
2+
3+
public readonly ref struct TupleAccessor<T1, T2, T3>(ref (T1, T2, T3) tuple) : ITupleAccessor
4+
{
5+
private readonly ref (T1, T2, T3) _tuple = ref tuple;
6+
public readonly int Length => 3;
7+
public TypedRef this[int index] => index switch
8+
{
9+
0 => TypedRef.Create(ref _tuple.Item1),
10+
1 => TypedRef.Create(ref _tuple.Item2),
11+
2 => TypedRef.Create(ref _tuple.Item3),
12+
_ => throw new IndexOutOfRangeException(),
13+
};
14+
}
15+
16+
partial class Accessor
17+
{
18+
public static TupleAccessor<T1, T2, T3> Create<T1, T2, T3>(ref this (T1, T2, T3) tuple) => new(ref tuple);
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace ConsoleApp1;
2+
3+
public ref struct TupleEnumerator<TAccessor>(TAccessor tuple)
4+
where TAccessor : struct, ITupleAccessor, allows ref struct
5+
{
6+
private TAccessor _tuple = tuple;
7+
private int _index = -1;
8+
9+
public bool MoveNext()
10+
{
11+
return ++_index < _tuple.Length;
12+
}
13+
public TypedRef Current => _tuple[_index];
14+
}
15+
16+
public static partial class Accessor
17+
{
18+
public static TupleEnumerator<TAccessor> GetEnumerator<TAccessor>(this TAccessor tuple)
19+
where TAccessor : struct, ITupleAccessor, allows ref struct
20+
=> new(tuple);
21+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace ConsoleApp1;
4+
5+
/// <summary>
6+
/// 型情報 + ref。
7+
/// </summary>
8+
/// <remarks>
9+
/// object だと値型の処理が悲惨なので ref で untyped にやり取りするための型を用意。
10+
///
11+
/// 中身、<see cref="TypedReference"/> とほぼ一緒。
12+
/// ただ、そっちは ref field 実装前からあるせいで相当特別な制限かかってて使いづらい。
13+
///
14+
/// 名前一緒だと混乱しそうなのであえて省略形。
15+
/// </remarks>
16+
public readonly ref struct TypedRef(Type type, ref byte r)
17+
{
18+
public readonly Type Type = type;
19+
public readonly UnsafeRef UnsafeReference = new(ref r);
20+
21+
public static TypedRef Create<T>(ref T value)
22+
{
23+
return new TypedRef(typeof(T), ref Unsafe.As<T, byte>(ref value));
24+
}
25+
26+
public ref T As<T>()
27+
{
28+
if (typeof(T) != Type) ThrowTypeNotMatched();
29+
return ref UnsafeReference.As<T>();
30+
}
31+
32+
public void Set<T>(T value)
33+
{
34+
if (!typeof(T).IsAssignableTo(Type)) ThrowTypeNotMatched();
35+
UnsafeReference.As<T>() = value;
36+
}
37+
38+
public T Get<T>()
39+
{
40+
if (!typeof(T).IsAssignableFrom(Type)) ThrowTypeNotMatched();
41+
return UnsafeReference.As<T>();
42+
}
43+
44+
private void ThrowTypeNotMatched() => throw new InvalidCastException();
45+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace ConsoleApp1;
4+
5+
/// <summary>
6+
/// ref。
7+
/// </summary>
8+
/// <remarks>
9+
/// Unsafe.As&lt;T, byte&gt; で ref byte 化して参照を持つ。
10+
/// ほぼ managed ポインター。
11+
/// ref byte 直伝搬よりはマシ、程度。
12+
/// </remarks>
13+
public readonly ref struct UnsafeRef(ref byte r)
14+
{
15+
public readonly ref byte UnsafeReference = ref r;
16+
17+
public ref T As<T>()
18+
{
19+
return ref Unsafe.As<byte, T>(ref UnsafeReference);
20+
}
21+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<Solution>
2+
<Project Path="ConsoleApp1/ConsoleApp1.csproj" />
3+
</Solution>

0 commit comments

Comments
 (0)