Skip to content

Instantly share code, notes, and snippets.

@matarillo
Last active August 29, 2015 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matarillo/190e1cc7e3b73b937061 to your computer and use it in GitHub Desktop.
Save matarillo/190e1cc7e3b73b937061 to your computer and use it in GitHub Desktop.

#01 型に属する情報をジェネリックに扱う試み ... のC#版

C# でも型パラメータのクラスメンバーは参照できない。Javaと同じ。

(追記)厳密には

型変数Tが型を表現する変数だからと言って、型に属する、つまりstaticなフィールドやメソッドへのアクセスはできない。

は同じなんだけど

Javaのジェネリクスはあくまでインスタンスに対してのものだという制限がある。

は、.NETの場合はちょっと違う。List<int>List<string>は別の型だ。

using System;

class Program
{
    static void Main(string[] args)
    {
    }

    static void FooMethod<T>(T value) where T : Foo
    {
        Console.WriteLine(T.Bar); // 'T' は '型パラメーター' ですが、指定されたコンテキストでは有効ではありません
    }
}

class Foo
{
    public static int Bar { get; set; }
}

で、(追記:staticメンバを利用したいのなら)型情報をインスタンスに持たせるしかないのは同じ。なので、もし

やりたいことはX型を二つのクラスで共有すること、そして互いの型を知ることだった。

これをやりたいのなら、

Javaのジェネリクスで独立した2つのクラス間でこの型を共有しようとすると先のような複雑な型変数の境界を記述しなければならない。独立した2つのクラスならば。独立じゃない従属関係にあるクラスならば型変数が共有できる。

については同じなのだけど、

そう、エンクロージング型内部クラスだ。

は.NETの場合は別の方法がとれる。静的ネストクラス(Javaでは「トップレベルネストクラス」かな?)でよいからだ。 もっと言えば、多態がいらないんだったらクラス(参照型)でなくてもいい。 たとえばこんな風に書ける。

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(IntegerValueType.Instance.Max.UnderlyingValue);
    }
}

public abstract class ValueType<X>
{
    public abstract Value Max { get; }
    public abstract Value Min { get; }

    public Value Create(X value)
    {
        return new Value(this, value);
    }

    public struct Value
    {
        public ValueType<X> Type;
        public X UnderlyingValue;

        public Value(ValueType<X> type, X value)
        {
            this.Type = type;
            this.UnderlyingValue = value;
        }
    }
}

public class IntegerValueType : ValueType<int>
{
    public readonly static IntegerValueType Instance = new IntegerValueType();

    public override ValueType<int>.Value Max
    {
        get { return Instance.Create(int.MaxValue); }
    }

    public override ValueType<int>.Value Min
    {
        get { return Instance.Create(int.MinValue); }
    }
}

(ちなみに、.NETにはSystem.ValueTypeという別の型がすでにあるので、この名前だとちょっとな……という感じはあるが、まあサンプルなので気にしない)

しかし、こういう型設計は無駄に複雑じゃないか?とも思う。つまり、

ValueTypeとValueがお互いの型を知ろうとする

という必要は本当にあるのか?という意味。

別に

public interface IArithmetic<T>
{
    T Zero { get; }
    T MaxValue { get; }
    T MinValue { get; }

    T Add(T x, T y);
    T Subtract(T x, T y);
    T Multiply(T x, T y);
    T Divide(T x, T y);
    T Modulo(T x, T y);
    T Negate(T x);
}

public class ArithmeticInt32 : IArithmetic<int>
{
    private ArithmeticInt32() { }

    public static readonly ArithmeticInt32 Instance = new ArithmeticInt32();

    public int Zero
    {
        get { return 0; }
    }

    public int MaxValue
    {
        get { return int.MaxValue; }
    }

    public int MinValue
    {
        get { return int.MinValue; }
    }

    public int Add(int x, int y)
    {
        return x + y;
    }

    public int Subtract(int x, int y)
    {
        return x - y;
    }

    public int Multiply(int x, int y)
    {
        return x * y;
    }

    public int Divide(int x, int y)
    {
        return x / y;
    }

    public int Modulo(int x, int y)
    {
        return x % y;
    }

    public int Negate(int x)
    {
        return -x;
    }
}

こんな感じにしておいて、何か演算をしたいときにはいつもIArithmetic<T>のインスタンスを渡してもらってもいいじゃないか、という気はする。面倒っちゃあ面倒だけど。そりゃあ、Haskellみたいに型クラスがあったり、Scalaみたいに型クラス的な動きを実現できたりするなら、それが一番いいんだけどね……

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment