Genericsとunsafe

某所より。

byte* array;
int index;
struct S;
とあって、C++でいうところの
S S* = (S*)&(array + index);
としたいのですけど、これと等価なC#のコードを教えてくださいませ。

これに対し、fixedを使えばいいと回答しましたが、

それ、genericsを使って、byte → Tとして、where T : struct
と書いた場合、コンパイルが通らないのは何故でしょう?(´ω`)

*おおっと、レテポーターgenerics*との事。そーいや、私はgenericsをunsafeでで使ってみたことがありませんでした。取りあえず何も考えずにストレートなコードを書いてみます。
(追記)

handle = GCHandle.Alloc(data, GCHandleType.Pinned);

をしないとMarshal.UnsafeAddrOfPinnedArrayElementは安全に使えません。間違った情報を広めてしまいそうなので追記しておきます。

static void Foo<T>(T[] t) where T : struct
{
    unsafe
    {
        // マネージ型のアドレスの取得、マネージ型のサイズの取得、
        // またはマネージ型へのポインタの宣言が実行できません('T')
        fixed (T* p = &t[0])
        {
            Point* pt = (Point*)p;
        }
    }
}

コンパイルエラーです。内容は上記コードのコメントにある通り。genericsの制約条件にstructを付けているのに何でだろ?・・・あっ、structのメンバーに参照型を入れることが可能なのでstruct制約ではポインタ型に変換できる保証はありませんね。それならば、Tをbyteに変換できれば何とかなるかな?とか考えて、こんな事をやってみました。

static void Foo<T> (T t, int size) where T : struct
{
    // genericな配列をbyteに変換する
    // BlockCopyは実行時チェックなのでコンパイル出来る
    byte[] bytes = new byte[size];
    Buffer.BlockCopy(t, 0, bytes, 0, bytes.Length);
    unsafe
    {
        fixed (byte* p = &bytes[0])
        {
            Point* pt = (Point*)p;
        }
    }
}

BlockCopyならコンパイル時にエラーにしないので上手く行きました。が、

> T -> byte変換が入ってしまいますが、

そのoverheadが許容できない…(´ω`)

と、お許しになりません。(^^;
まぁ、実際問題4096byteのバッファでテストしたら2桁ほどオーダーが違っているので、確かに駄目っぽいのですが。諦めようかと思ったのですが、この手の処理だとMarshalクラスに何か使えるモノがあったりすることを思い出して探してみたところビンゴでした。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;

public struct Point
{
    public int X;
    public int Y;
    public int Z;
}

class Program
{
    static void Foo(byte bytes)
    {
        unsafe
        {
            fixed (byte* p = &bytes[0])
            {
                Point* pt = (Point*)p;
            }
        }
    }

    static void Foo<T>(T t) where T : struct
    {
        IntPtr p = Marshal.UnsafeAddrOfPinnedArrayElement(t, 0);
        unsafe
        {
            Point* pt = (Point*)p;
        }

    }

    const int LOOP = 100000000;
    static void Main(string args)
    {
        byte bytes = new byte[4096];

        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < LOOP; ++i)
            Foo(bytes);
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);

        sw.Reset();
        sw.Start();
        for (int i = 0; i < LOOP; ++i)
            Foo<byte>(bytes);
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

/* 結果
591
729
 */

これにて解決、かな?

Marshal.UnsafeAddrOfPinnedArrayElement

これが勝利の鍵だ。