Skip to content

Instantly share code, notes, and snippets.

@ufcpp
Created September 28, 2017 01:47
Show Gist options
  • Save ufcpp/d1d2ce3f925afdc39a288591e51c2709 to your computer and use it in GitHub Desktop.
Save ufcpp/d1d2ce3f925afdc39a288591e51c2709 to your computer and use it in GitHub Desktop.
道を踏み外すかどうかの瀬戸際な共変アップキャスト
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
interface ICovariant<T> // Task<T> とか (bool, T) を含むので out は付けれない
where T : class
{
T A();
Task<T> B();
(bool, T) C();
}
class Covariant<T> : ICovariant<T> // クラスなので当然 out 付けれない
where T : class
{
T _value;
public Covariant(T value) => _value = value;
public T A() => _value;
public async Task<T> B()
{
await Task.Delay(1);
return _value;
}
public (bool, T) C() => (_value != null, _value);
}
class Base { }
class Derived : Base { }
class Program
{
static void Main()
{
Covariant<Derived> d = new Covariant<Derived>(new Derived());
// unsafe な感じで強制的に共変「アップキャスト」
//
// ※ Task<T> とか (bool, T) とかの共変な代入は、
// 「これが安全である」というのを機械的に調べられないのでインターフェイスに out を付けれない。
// でも、コンパイラーが判定できないだけであって、実際のところ共変な代入しても問題なかったりする。
Covariant<Base> b = Upcast(d);
// OK。ちゃんと動く
// ただし、これ、言語仕様的に大丈夫な保証は全くない。
// 「Covariant<Base> と Covariant<Derived> のメソッドテーブルの構造が同じだから結果的に動いてる」的な危うい動作なはず。
ClassWrite(b);
// インターフェイスを介するとダメ。
// Unhandled Exception: System.EntryPointNotFoundException: Entry point was not found.
// Covariant<Derived> は ICovariant<Base> を実装していないので、テーブル引けない。
// ICovariant には out が付いていないので、ICovariant<Derived> を ICovariant<Base> 扱いはできない。
InterfaceWrite(b);
}
private static void ClassWrite(Covariant<Base> b)
{
// callvirt instance !0 class Covariant`1<class Base>::A()
Console.WriteLine(b.A());
// callvirt instance class [mscorlib]System.Threading.Tasks.Task`1<!0> class Covariant`1<class Base>::B()
Console.WriteLine(b.B().Result);
// callvirt instance valuetype [mscorlib]System.ValueTuple`2<bool, !0> class Covariant`1<class Base>::C()
Console.WriteLine(b.C());
}
// 引数がインターフェイスになっている以外は ClassWrite と全く同じ。
private static void InterfaceWrite(ICovariant<Base> b)
{
// callvirt instance !0 class ICovariant`1<class Base>::A()
Console.WriteLine(b.A());
// callvirt instance class [mscorlib]System.Threading.Tasks.Task`1<!0> class ICovariant`1<class Base>::B()
Console.WriteLine(b.B().Result);
// callvirt instance valuetype [mscorlib]System.ValueTuple`2<bool, !0> class ICovariant`1<class Base>::C()
Console.WriteLine(b.C());
}
// 強制共変アップキャスト。
// StructLayout(Explicit) を使った無理やりな方法。
// System.Runtime.CompilerServices.Unsafe.As<Covariant<Derived>, Covariant<Base>>(ref d) とかでも OK。
static Covariant<Base> Upcast(Covariant<Derived> d)
{
Union u = default;
u.d = d;
return u.b;
}
[StructLayout(LayoutKind.Explicit)]
struct Union
{
[FieldOffset(0)]
public Covariant<Base> b;
[FieldOffset(0)]
public Covariant<Derived> d;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment