Created
September 28, 2017 01:47
-
-
Save ufcpp/d1d2ce3f925afdc39a288591e51c2709 to your computer and use it in GitHub Desktop.
道を踏み外すかどうかの瀬戸際な共変アップキャスト
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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