C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2011/03/22 00:03

C#と諸々 ローカル変数に参照渡し

ローカル変数に参照渡しはできないけれど、参照を扱うことはできるということに気付きました。
実は C# には隠しキーワードがいくつかあって、その内の __makeref と __refvalue を使うことで参照を扱うことができます。

using System;
class Program
{
    static void Main()
    {
        int v0 = 0;
        TypedReference r0 = __makeref(v0);
        __refvalue(r0, int) = 5;

        Console.WriteLine(v0); //=> 5
    }
}


スポンサーサイト



タグ: .NET C#
2011/03/21 01:50

struct Hoge
{
    public void Clear()
    {
        this = new Hoge();
    }
}


ちょっと驚きましたが、よくよく考えると出来て当然ですね。

タグ: .NET C#
2011/03/07 13:22

「C#できます」と言い放ったあいつがJavaプログラマであることを見分ける10の質問 - 猫とC#について書くmatarilloの日記

仕事で C# 使わなくなって久しい僕なので、あまり自信ないけど…。


* ==演算子のオーバーロードを実装してEqualsメソッドと同じ処理を実行するようにしてもよい場合はどのような時か?
同値であるオブジェクトを同一であるとして扱う場合。

* ループ内でなければ、たとえ100個の文字列型変数であってもまとめて+演算子で連結してよい理由を説明せよ。
+演算子が99回繰り返されるのではなく100個の文字列を一つにまとめるという処理に最適化されるため。

* List<int>のように値型を格納するジェネリックコレクションを使ってもボックス化/ボックス化解除が発生しない理由を説明せよ。
実行時に、要素の型が内部的にも object 型ではなく実際のジェネリック引数の型で置換されて動作するため。

* Full GC(Gen2 GC)が動作したときに断片化していてもコンパクションされないヒープ領域はどのような領域か?
ラージオブジェクトヒープと呼ばれる、一定サイズ以上のサイズを持ったオブジェクトが配置される領域。

* throw; とthrow ex; の違いをスタックトレースの観点で説明せよ。
前者は catch するまでのスタックトレースを維持するが、後者はその時点からのスタックトレースで上書きされる。

* フィールドのアクセス修飾子をprivateにしプロパティのgetter/setterではそのフィールドを読み書きするだけというコードが馬鹿馬鹿しい理由を説明せよ。
プロパティで単一フィールドの読み書き以外を行わない場合、自動プロパティが使用できるため。

* nullを参照している参照型変数のメソッドを呼び出そうとした場合でもNullReferenceExceptionが発生しないのは主にどういう状況か?
第一引数が null であっても NullReferenceException が発生しない拡張メソッドである場合。

* クラスと構造体の違いは何か?(「スタックとヒープ」以外で)
全部「スタックとヒープ」に起因するけど、次のような違いがある。(スタックとヒープに起因しないものあるっけ?)
・継承できない
・ユーザー定義のデフォルトコンストラクタとファイナライザを用意できない
・== 演算子、!= 演算子がデフォルトでは用意されていない
・object や interface にキャストするとボックス化が生じる

* デストラクタとは何か?
オブジェクトが破棄される際に呼び出される特殊なメソッド。C# ではファイナライザと呼ぶ。

* インターフェースの明示的実装を利用する目的を1つ説明せよ。
対象のインターフェイスを意識していないコードから無闇に呼び出せないように隠蔽する。



タグ: .NET C#
2010/08/04 21:00
次のような 2 つのクラスがあったとします。

class A
{
}

class B : A
{
}

これらのクラスそれぞれに対し、暗黙的に型変換可能なクラス、A1 クラスと B1 クラスを用意するとします。

class A1
{
    public A1(A source)
    {
        this._source = source;
    }

    private readonly A _source;

    public static implicit operator A(A1 target)
    {
        return target._source;
    }

    public static implicit operator A1(A target)
    {
        return new A1(target);
    }
}

class B1
{
    public B1(B source)
    {
        this._source = source;
    }

    private readonly B _source;

    public static implicit operator B(B1 target)
    {
        return target._source;
    }

    public static implicit operator B1(B target)
    {
        return new B1(target);
    }
}

A1 と B1 は継承関係にありませんが、A と B は継承関係にあります。
そこで、次のように B1 のインスタンスを A1 に型変換できるようにしたいとします。ただし、A1 と B1 はお互いの存在を直接知らないものとします。更に、A1 は A の派生クラスの存在を直接知らないものとします。

B1 b = new B();
A1 a = b;

A1 と B1 はお互いの存在を直接知らないので、まずは間接的に知ることができるよう、A1 と B1 が共通で実装するインターフェイスを用意します。

interface ITypeDef<out T>
{
    T Source
    {
        get;
    }
}

このインターフェイスを実装したコードは次のようになります。

class A1 : ITypeDef<A>
{
    public A1(A source)
    {
        this._source = source;
    }

    protected readonly A _source;

    public static implicit operator A(A1 target)
    {
        return target._source;
    }

    public static implicit operator A1(A target)
    {
        return new A1(target);
    }

    A ITypeDef<A>.Source
    {
        get
        {
            return this._source;
        }
    }
}

class B1 : ITypeDef<B>
{
    public B1(B source)
    {
        this._source = source;
    }

    protected readonly B _source;

    public static implicit operator B(B1 target)
    {
        return target._source;
    }

    public static implicit operator B1(B target)
    {
        return new B1(target);
    }

    B ITypeDef<B>.Source
    {
        get
        {
            return this._source;
        }
    }
}

ITypeDef<T> の型パラメータ T は共変ですので、ITypeDef<B> を ITypeDef<A> に型変換することが可能です。ということは、A1 が ITypeDef<A> からの変換をサポートすれば、ITypeDef<B> を実装する B1 からの変換ができるようになるはずです。

class A1 : ITypeDef<A>
{
    public A1(A source)
    {
        this._source = source;
    }

    protected readonly A _source;

    public static implicit operator A(A1 target)
    {
        return target._source;
    }

    public static implicit operator A1(A target)
    {
        return new A1(target);
    }

    public static implicit operator A1(ITypeDef<A> target)
    {
        return new A1(target.Source);
    }

    A ITypeDef<A>.Source
    {
        get
        {
            return this._source;
        }
    }
}

class B1 : ITypeDef<B>
{
    public B1(B source)
    {
        this._source = source;
    }

    protected readonly B _source;

    public static implicit operator B(B1 target)
    {
        return target._source;
    }

    public static implicit operator B1(B target)
    {
        return new B1(target);
    }

    public static implicit operator B1(ITypeDef<B> target)
    {
        return new B1(target.Source);
    }

    B ITypeDef<B>.Source
    {
        get
        {
            return this._source;
        }
    }
}

残念ながら、このコードはコンパイルが通りません。なぜか C# ではインターフェイスからの変換演算子の定義が禁止されているからです。


さて、今回の目的は恐らく正攻法では実現できません。
ということで、これから非実用的な実現方法を紹介します。

先にも記述しました通り、変換演算子で変換元または変換先をインターフェイスにすることはできません。
そこでジェネリックの型パラメータを利用します。ジェネリックの型パラメータは変換元や変換先に指定することができます。そして、型引数としてインターフェイスを指定することで、間接的に変換元または変換先をインターフェイスにすることができます。
例えば、次のコードでは IEnumerable<int> から Class1<IEnumerable<int>> への変換が実現されます。

using System.Collections.Generic;

class Class1<T>
{
    public Class1(T source)
    {
        this._source = source;
    }

    private readonly T _source;

    public static implicit operator Class1<T>(T target)
    {
        return new Class1<T>(target);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Class1<IEnumerable<int>> c = new List<int>();
    }
}

Class1<T> の型パラメータに IEnumerable<int> と指定していますので、変換演算子は実質次のような形と同等になります。

    public static implicit operator Class1<IEnumerable<int>>(IEnumerable<int> target)
    {
        return new Class1<IEnumerable<int>>(target);
    }

では、話を元に戻して ITypeDef<A> から A1 への変換を実現してみます。
A1 はあくまでも非ジェネリッククラスのままにしたいので、先ほどの Class1<T> よりも更に複雑になります。
A1<T> クラスを用意して、これを A1 が継承するようにします。
A1<T> は A1 のためだけに存在しますので、A1 の定義以外のコードからは直接使用しないようにします。

abstract class A1<T>
    where T : ITypeDef<A>
{
    public static implicit operator A1<T>(T target)
    {
        return (A1<T>)(object)new A1(target.Source);
    }
}

class A1 : A1<ITypeDef<A>>, ITypeDef<A>
{
    public A1(A source)
    {
        this._source = source;
    }

    protected readonly A _source;

    public static implicit operator A(A1 target)
    {
        return target._source;
    }

    public static implicit operator A1(A target)
    {
        return new A1(target);
    }

    A ITypeDef<A>.Source
    {
        get
        {
            return this._source;
        }
    }
}

B1 についても同じようにしておきます。

abstract class B1<T>
    where T : ITypeDef<B>
{
    public static implicit operator B1<T>(T target)
    {
        return (B1<T>)(object)new B1(target.Source);
    }
}

class B1 : B1<ITypeDef<B>>, ITypeDef<B>
{
    public B1(B source)
    {
        this._source = source;
    }

    protected readonly B _source;

    public static implicit operator B(B1 target)
    {
        return target._source;
    }

    public static implicit operator B1(B target)
    {
        return new B1(target);
    }

    B ITypeDef<B>.Source
    {
        get
        {
            return this._source;
        }
    }
}

ところで、A1<T> に定義されている変換演算子は、あくまでも A1 ではなく A1<T> への変換演算子になっています。
つまり、B1 から A1 に変換する際の内部的な流れとしては B1 → A1<T> → A1 という二段階の流れになります。
A1<T> から A1 への変換はダウンキャストとなるため明示的な変換が必要になります。
というわけで、B1 から A1 への変換は暗黙的にはできません。次のように明示的な変換となります。

B1 b = new B();
A1 a = (A1)b;

以上で当初の目的は一応達成できました。
仕組みが複雑なので実用度は低いですね。


2009/06/15 01:33
bleis-tift さんが前に言ってた、D 言語の強い typedef が C# にも欲しいなぁです。
まぁ無いものはしょうがないってことで、お遊びでこんなクラス作ってみました。

Int32Def(TSelf).cs
using System;
using System.Runtime.Serialization;
using System.Reflection;

public abstract class Int32Def<TSelf>
    where TSelf : Int32Def<TSelf>
{
    private readonly int _value;

    public static implicit operator int(Int32Def<TSelf> value)
    {
        return value._value;
    }

    public static explicit operator Int32Def<TSelf>(int value)
    {
        TSelf obj = (TSelf)FormatterServices.GetUninitializedObject(typeof(TSelf));
        FieldInfo valueField = typeof(Int32Def<TSelf>).GetField("_value", BindingFlags.NonPublic | BindingFlags.Instance);
        valueField.SetValue(obj, value);
        return obj;
    }

    public static TSelf operator +(Int32Def<TSelf> value)
    {
        return (TSelf)(+value._value);
    }

    public static TSelf operator -(Int32Def<TSelf> value)
    {
        return (TSelf)(-value._value);
    }

    public static TSelf operator ~(Int32Def<TSelf> value)
    {
        return (TSelf)(~value._value);
    }

    public static TSelf operator ++(Int32Def<TSelf> value)
    {
        int temp = value._value;
        temp++;
        return (TSelf)(temp);
    }

    public static TSelf operator --(Int32Def<TSelf> value)
    {
        int temp = value._value;
        temp--;
        return (TSelf)(temp);
    }

    public static TSelf operator +(Int32Def<TSelf> value1, Int32Def<TSelf> value2)
    {
        return (TSelf)(value1._value + value2._value);
    }

    public static TSelf operator -(Int32Def<TSelf> value1, Int32Def<TSelf> value2)
    {
        return (TSelf)(value1._value - value2._value);
    }

    public static TSelf operator *(Int32Def<TSelf> value1, Int32Def<TSelf> value2)
    {
        return (TSelf)(value1._value * value2._value);
    }

    public static TSelf operator /(Int32Def<TSelf> value1, Int32Def<TSelf> value2)
    {
        return (TSelf)(value1._value / value2._value);
    }

    public static TSelf operator %(Int32Def<TSelf> value1, Int32Def<TSelf> value2)
    {
        return (TSelf)(value1._value % value2._value);
    }

    public static TSelf operator &(Int32Def<TSelf> value1, Int32Def<TSelf> value2)
    {
        return (TSelf)(value1._value & value2._value);
    }

    public static TSelf operator |(Int32Def<TSelf> value1, Int32Def<TSelf> value2)
    {
        return (TSelf)(value1._value | value2._value);
    }

    public static TSelf operator ^(Int32Def<TSelf> value1, Int32Def<TSelf> value2)
    {
        return (TSelf)(value1._value ^ value2._value);
    }

    public static TSelf operator <<(Int32Def<TSelf> value1, int value2)
    {
        return (TSelf)(value1._value << value2);
    }

    public static TSelf operator >>(Int32Def<TSelf> value1, int value2)
    {
        return (TSelf)(value1._value >> value2);
    }
}


Program.cs
using System;

class Hoge : Int32Def<Hoge>
{
}

class Fuga : Int32Def<Fuga>
{
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Test((Hoge)5, (Hoge)10)); //=> 15
        Console.WriteLine(Test((Fuga)5, (Fuga)10)); //=> 50
    }

    static Hoge Test(Hoge a, Hoge b)
    {
        return a + b;
    }

    static Fuga Test(Fuga a, Fuga b)
    {
        return a * b;
    }
}



あくまでもお遊びです。Int32 限定ですし、構造体ではなくクラス (従って null がありえますし現時点では対策してません) です。あと、演算子しか考慮してません。

ちなみに T4 Template 版もあります。まだ詰めが甘いですが。こちらは構造体としてコード生成するので、null を許容しません。あと、Int32 以外のプリミティブ型もたぶん行けます、たぶん。
こっちはもうちょい頑張れば実用的なものにできるかなと思います。

<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#
    string namespaceName = "Samples";
    string newTypeName = "Hoge";
    string sourceTypeName = "int";
   
    string[] unaryOperators = new string[]
    {
        "+",
        "-",
        "~",
        //"!",
    };
   
    string[] destructiveUnaryOperators = new string[]
    {
        "++",
        "--",
    };
   
    string[] binaryOperators = new string[]
    {
        "+",
        "-",
        "*",
        "/",
        "%",
        "&",
        "|",
        "^",
    };
   
    string[] booleanOperators = new string[]
    {
        "true",
        "false"
    };
   
    string[] shiftOperators = new string[]
    {
        "<<",
        ">>"
    };
   
    string[] comparisonOperators = new string[]
    {
        "==",
        "!=",
        "<",
        ">",
        "<=",
        ">="
    };
#>
namespace <#= namespaceName #>
{
    public struct <#= newTypeName #>
    {
        public <#= newTypeName #>(<#= sourceTypeName #> value)
        {
            this._value = value;
        }
       
        private readonly <#= sourceTypeName #> _value;
       
        public static implicit operator <#= sourceTypeName #>(<#= newTypeName #> value)
        {
            return value._value;
        }
       
        public static explicit operator <#= newTypeName #>(<#= sourceTypeName #> value)
        {
            return new <#= newTypeName #>(value);
        }
<#
    foreach (string unaryOperator in unaryOperators)
    {
#>

        public static <#= newTypeName #> operator <#= unaryOperator #>(<#= newTypeName #> value)
        {
            return new <#= newTypeName #>(<#= unaryOperator #>value._value);
        }
<#
    }
#>
<#
    foreach (string destructiveUnaryOperator in destructiveUnaryOperators)
    {
#>

        public static <#= newTypeName #> operator <#= destructiveUnaryOperator #>(<#= newTypeName #> value)
        {
            <#= sourceTypeName #> temp = value._value;
            temp<#= destructiveUnaryOperator #>;
            return new <#= newTypeName #>(temp);
        }
<#
    }
#>
<#
    foreach (string binaryOperator in binaryOperators)
    {
#>

        public static <#= newTypeName #> operator <#= binaryOperator #>(<#= newTypeName #> value1, <#= newTypeName #> value2)
        {
            return new <#= newTypeName #>(value1._value <#= binaryOperator #> value2._value);
        }
<#
    }
#>
<#
    foreach (string shiftOperator in shiftOperators)
    {
#>

        public static <#= newTypeName #> operator <#= shiftOperator #>(<#= newTypeName #> value1, int value2)
        {
            return new <#= newTypeName #>(value1._value <#= shiftOperator #> value2);
        }
<#
    }
#>
<#
/*
    foreach (string booleanOperator in booleanOperators)
    {
#>

        public static bool operator <#= booleanOperator #>(<#= newTypeName #> value)
        {
            throw new System.NotImplementedException();
        }
<#
    }
*/
#>
    }
}
2009/06/13 00:28
前回の続きです。
頂いたコメントを参考に更なるリファクタリングを行いました。

class Cards
{
    public string[] Deal(int numPlayers, string deck)
    {
        int needCount = deck.Length - (deck.Length % numPlayers);
        string[] hands = Enumerable.Repeat(string.Empty, numPlayers).ToArray();

        for (int cardIndex = 0; cardIndex < needCount; cardIndex++)
        {
            int playerIndex = cardIndex % numPlayers;
            hands[playerIndex] += deck[cardIndex];
        }

        return hands;
    }
}

かなりすっきりしました。
前回のリファクタリングで導入した yield return による順序のオブジェクト化は半分ネタだったのでともかくとしても、TrimDeck メソッドは今にして思うと過剰なリファクタリングだった気がします。(でも、順序のオブジェクト化に伴い for 文を foreach 文にしたくって TrimDeck という形を取った、というのもあるんですよ^^;)
CreateInitialHands メソッドも、Enumerable.Repeat の導入によって不要となりましたね。
ちなみに、メソッドの中身が一行だとしても、場合によってはメソッド化する意味があります。処理に名前を付けることで可読性の向上が期待できる場合があるからです。しかし、今回の場合は、メソッド化せずともローカル変数の名前で処理の目的を示すことができますので、インライン化を行いました。
needCount については、前回のようにまず needlessCount を求めてから needCount を求めるようにする方法と、今回のように一行にする方法と、どちらの方が良かったのか悩み所でしたが…。

最後に for 文に関してですが、前回の記事のコメント欄では hands[i % numPlayers] += deck[i]; と書きましたが、そうはせずにまず playerIndex を求めるようにしました。これは、i % numPlayers が何を求めているのかわかりづらいかなと思ってのことです。前述の通り、ローカル変数の名前によって可読性が向上したのではないかと思います。

ということで、かなりいい感じになったなぁと思いますがどうでしょ?
タグ: .NET C#
2009/06/09 22:37

[どうでもよいこと]10分コーディング | Ryuzee.com
10分でコーディング|プログラミングに自信があるやつこい!!


問題読むのに2分ちょい、問題解くのに5分ちょいでした。

⊂(^ω^)⊃ セフセフ!!

class Cards
{
    public string[] Deal(int numPlayers, string deck)
    {
        int count = deck.Length - deck.Length % numPlayers;
        string[] result = new string[numPlayers];

        for (int i = 0; i < numPlayers; i++)
        {
            result[i] = string.Empty;
        }

        int playerIndex = 0;
        for (int i = 0; i < count; i++)
        {
            result[playerIndex] += deck[i];
            playerIndex++;
            if (numPlayers == playerIndex)
            {
                playerIndex = 0;
            }
        }
        return result;
    }
}



終了後に、リファクタリングもしてみました。
yield return による、順序のオブジェクト化が面白いかもです。

class Cards
{
    public string[] Deal(int numPlayers, string deck)
    {
        string trimmedDeck = this.TrimDeck(deck, numPlayers);
        string[] hands = this.CreateInitialHands(numPlayers);

        IEnumerator<int> order = this.CreateDealOrder(numPlayers);
        foreach (char card in trimmedDeck)
        {
            order.MoveNext();
            hands[order.Current] += card;
        }
        return hands;
    }

    private string TrimDeck(string deck, int numPlayers)
    {
        int needlessCount = deck.Length % numPlayers;
        int needCount = deck.Length - needlessCount;
        return deck.Substring(0, needCount);
    }

    private string[] CreateInitialHands(int numPlayers)
    {
        string[] hands = new string[numPlayers];
        for (int i = 0; i < numPlayers; i++)
        {
            hands[i] = string.Empty;
        }
        return hands;
    }

    private IEnumerator<int> CreateDealOrder(int numPlayers)
    {
        const int startIndex = 0;
        int currentIndex = startIndex;
        while (true)
        {
            yield return currentIndex;

            int incrementedIndex = currentIndex + 1;
            currentIndex = (incrementedIndex < numPlayers) ? incrementedIndex : startIndex;
        }
    }
}

続き → C#と諸々 Refactoring:10分でコーディング
タグ: .NET C#
2007/09/22 23:29
anarchy golf - FizzBuzz

C# の最短コードは 123 文字です。たぶんこれ以上短くはできないと思われます。
これがそのコードになります。

class P{static void Main(){for(int i=0;i++<100;)System.Console.WriteLine((i%3<1?"Fizz":"")+(i%5<1?"Buzz":i%3<1?"":i+""));}}



改行とスペースを入れて見やすくするとこんな感じです。

class P
{
    static void Main()
    {
        for (int i = 0; i++ < 100; )
            System.Console.WriteLine((i % 3 < 1 ? "Fizz" : "") + (i % 5 < 1 ? "Buzz" : i % 3 < 1 ? "" : i + ""));
    }
}


最大のポイントは、やはり WriteLine メソッドの引数です。あと、for 文の書き方もポイントですね。普段こんな書き方してたら、怒られても文句言えませんけど ( 笑 )
タグ: .NET C# FizzBuzz
2007/02/20 02:10
久々のコーディング俺規約です。
R.田中一郎さんの記事を読んで、僕もふと、コーディングのたびに気になる事を思い出しました。

何かと言うと、usingブロック内で得た値を戻り値で返す場合です。
例えば、

public string Foo()
{
    using (Hoge hoge1 = new Hoge())
    {
        hoge1.Fuga = "ふがふが";
        string piyoResult = hoge1.Piyo():
        return piyoResult;
    }
}

と書くか、

public string Foo()
{
    string piyoResult;
    using (Hoge hoge1 = new Hoge())
    {
        hoge1.Fuga = "ふがふが";
        piyoResult = hoge1.Piyo():
    }
    return piyoResult;
}

と書くか。

前者はusingブロックの中でreturnして、後者はusingブロックを抜けた後でreturnしてます。
こういう時は、僕の場合前者、つまりusingブロックの中でreturnしてしまいます。

でも、hoge1オブジェクトの操作を終えた後、「他の処理」が少々入る場合は、

public string Foo()
{
    using (Hoge hoge1 = new Hoge())
    {
        hoge1.Fuga = "ふがふが";
        string piyoResult = hoge1.Piyo():
        string fooResult = piyoResult.Replace('-', '_')
        return fooResult;
    }
}

と書くのではなく、

public string Foo()
{
    string piyoResult;
    using (Hoge hoge1 = new Hoge())
    {
        hoge1.Fuga = "ふがふが";
        piyoResult = hoge1.Piyo():
    }
    string fooResult = piyoResult.Replace('-', '_')
    return fooResult;
}

という風に書きます。
理由は、「他の処理」を行う前にusingブロックを抜けることができるのに、無駄にhoge1を延命させてしまうのは無意味だからです。
「他の処理」が複数行に渡る場合、hoge1オブジェクトが既に不要となっていることが一目ではわかりづらいし、「他の処理」が重い場合、それだけ解放処理 ( Dispose ) が遅くなります。

こっちのパターンはこれでいいです。そんな気がします。
でも、最初のパターン、つまり「他の処理」が入らないパターンも、統一してusingブロックの前でreturnするべきな気がしてきます。

いやまてよ・・・、そもそもreturnの位置は重要でなく、usingブロックの範囲が重要なのかな。「他の処理」が入らずreturnするだけなら、usingブロック内に含めても何ら問題ないもんなぁ。その方が可読性は良いし。hoge1オブジェクトを操作する処理が複数行ある場合なんて特にそうですしね。
うん、そんな気がしてきた!そうすると、returnの位置は臨機応変ってことになりますね。

・・・ってことで、結局今まで通りでいいのかな?w
2007/01/17 11:48
言わずと知れたアンダース ヘルスバーグ氏が、日本の開発者の質問に答えてくれてます。

Ask The Experts! ~ その疑問、マイクロソフトの担当者にきいてみませんか ?

特設サイトもありました。
アンダース ヘルスバーグ 特別サイト


[ 情報元 ]
Hiroyasu Kitagawa's Blog : Ask the Experts 第3弾:C#アンダース再び
タグ: .NET C#