忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

C#でRubyっぽいループを書く

(この記事を書き終わってもしやと思いググッてみると
どうやら同じことを考えている人がすでに居たようです。残念)


4,5年前Rubyを使ったことがあったのですが、
ループがけっこう簡単です。
5.times { |i|
	puts i
}
Rubyを使ったことのない方のために言うと、
上の実行結果は
0
1
2
3
4

です。

これはたぶんC#よりも初学者にとって分かりやすいと思います。
なにせ「5回繰り返せ!」というのが
5.timesからぐっと伝わってくるからです。
(そう思いませんか!?)

私が初めて覚えたプログラミング言語はCでしたが、
当時中学生の身としてforはいささか複雑すぎました。
結果forに引っかかったことを覚えています。

C#でクロージャを使ってループ

しかしここで言いたいのはC#はダメだということではありません!
C#でもRubyのような書き方ができるからです。

まずC#で普通にループを書こうと思えば
以下のようにforを使います。
class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 5; i++)
        {
            System.Console.WriteLine(i);
        }
    }
}


結果は、上に書いたrubyのコードと同じです。
数字を0から4まで出力します。

C#でRubyのように書くにはどうすればいいでしょう?
ここで拡張メソッドの登場です。

static class Int32Extension
{
    public static void Times(this int loopCount, System.Action<int> loop)
    {
        for (int i = 0; i < loopCount; i++)
        {
            loop(i);
        }
    }
}


皆さんご存知のとおり上のように書くと、
System.Int32構造体に拡張メソッドを追加できます。
つまり以下のようにかけるのです。
class Program
{
    static void Main(string[] args)
    {
        5.Times(i => System.Console.WriteLine(i));
    }
}

いかがでしょうか。
forを使うよりシンプルになっているのは確かです。

ただ・・・これは良いものなのでしょうか悪いものなのでしょうか?
なるほどシンプルで書きやすいというのはそのとおりでしょうが、
forより明らかにパフォーマンスが落ちそうです。
もっともパフォーマンスはネックになるところ以外では気にするべきではありません。

では読みやすさはどうでしょうか?
個人的にこれを使ってみて、
「書きやすいけれどももしかするとほんの少し読みにくいかもしれない」
といった感想です。
読みにくいかもしれないというのはデリゲートに{}を使ったとき);と重なって
ちょっと汚く見えるかもしれないということです。

ただ、Timesの他にもUpToメソッドなどを書いて使ってみたのですが
そちらは});がごちゃごちゃしているのを差し引いても
かなり読みやすくなったと思います。

個人的見解としては、今後もループをたくさん使わなければいけないときにはこれを使うと思います、
といったところでしょうか。

拍手[0回]

PR


Visual C# 2008 Express EditionでWPFアプリケーションを作っているときツールバーからアイテムをドラッグ&ドロップできない

先月は引越しやら何やらでゴタゴタまみれですっかりブログのサボり癖がついてしまいました。
リハビリとしてまずはちょっとしたことを書いておきます。

WPFでアイテムをツールバーからドラッグ&ドロップできない

ちょっと前から気になっていたことがありました。
VC#2008でWPFを作ってるときに、アイテムをツールバーからドラッグ&ドロップできないという問題です。

WPF、つまりWindows Presentation Foundationは、その前世代(?)のWindowsFormsと同じように、ユーザーインターフェースをドラッグ&ドロップでかんたんに作ることが出来ます(少なくとも、そういうことになっています)。
creatingWPFApplication.jpg

左側にToolboxというのがあってその中にButtonとかCheckBoxとか色んなアイテムがあり、そのアイテムをマウスでドラッグ&ドロップすることで右側のウィンドウに貼り付けることが出来るわけですね。これはWindows Formsと同じです。

ところが、昨年からそうだったのですが、それをやろうとしても×印が出てドロップできませんでした(仕方が無くそのときはXAMLを手打ちしていました。ひどすぎる)。

解決方法

で、ちょっと調べてみたら出てきました。

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2431637&SiteID=1
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2616165&SiteID=1

次のような操作をXP Professionalでしたらドラッグ&ドロップが出来るようになったそうです(確かめました。僕もVistaで同じ操作をしたら確かにドラッグ&ドロップできるようになりました。でもまあ念のためやるのならバックアップをお願いします):

1.%userprofile%の中の以下のフォルダを削除する
    Local Settings\Application Data\Microsoft\VSCommon
    Local Settings\Application Data\Microsoft\VCSExpress
    Application Data\Microsoft\VCSExpress

(最後のApplication Data\Microsoft\VCSExpressは僕は消し忘れていたのですが、それでも上手くいきました)

2."My Document"で次のフォルダを削除
    Visual Studio 2008\Settings
    Visual Studio 2005\Settings

(Visual Studio 2005のほうは削除しなくてもいい気がしますが、ついついこっちも削除してしまい、検証は出来ませんでした)

3.regedit.exeでレジストリキー、HKEY_CURRENT_USER\Software\Microsoft\VCSExpressを削除する。

(これは必須です。これを削除しなければ相変わらずドラッグ&ドロップは出来ませんでした。これを削除して初めて、再びドラッグ&ドロップできるようになりました。)

3を終えてからVisual C# 2008 Express Editionを起動すると、初回起動のときの例の「数分かかることがあります」が表示され、
fewseconds.jpg(←例のダイアログ)
再びWPFで上手くアイテムをドラッグ&ドロップできるようになります。

検証していないのでなんともいえませんが、おそらくこの手順には無駄がかなりあります。本当に必要なのは、3とわずかなフォルダの削除だけだと思います。もしかしたら3だけでも上手くいくかもしれません


追記

メインコンピュータでもういっかい試したところ(上で試したのはセカンドのラップトップパソコン)、どうやら削除するフォルダは「1.」の"Local Settings\Application Data\Microsoft\VCSExpress"だけでいいっぽいですね。そのあと「3.」のレジストリキーの削除でうまくいきました。(さいしょは「3.」だけを行ったのですがムリでした。~~VCSExpressを削除して、また「3.」をやって、はじめてうまくいきました)

拍手[0回]


"Orcas"とC# 3.0 その2 拡張メソッド

C# 3.0 には拡張メソッドなるものがあるそうで、
あるクラスのメソッドを後から増やせるそうです。
using System;

class Person { public int Age { get; set; } }

static class PersonExtension
{
    public static bool IsAdult(this Person person) {
        return person.Age >= 20;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person child = new Person { Age = 10 };
        Person father = new Person { Age = 35 };
        Console.WriteLine(child.IsAdult()); //False
        Console.WriteLine(father.IsAdult()); //True 
    }
}

一見したところこれはクラスの凝集性を破壊するだけ
のように思えます。
別々に書く意味は全くありません。
PersonクラスにIsAdultメソッドも書くべきです。
実際、公式でもあまり軽々しく使うなと言われているようです。

ではなぜ拡張メソッドが存在するかと言うと、
1つにはインターフェースが実装しやすくなる
と言うのがあるようです。

using System;
using System.IO;

interface IPerson { int Age { get; set; } }
class Person : IPerson { public int Age { get; set; } }
class PersonFile : IPerson
{
    public int Age
    {
        get { return Int32.Parse(File.ReadAllText("person.txt")); }
        set { File.WriteAllText("person.txt", value.ToString()); }
    }
}

static class PersonExtension
{
    public static bool IsAdult(this IPerson person)
    {
        return person.Age >= 20;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person child = new Person { Age = 10 };
        PersonFile father = new PersonFile { Age = 35 };
        Console.WriteLine(child.IsAdult());
        Console.WriteLine(father.IsAdult());
    }
}
とりあえずPersonクラスに似た、PersonFileクラスを作りました。
メモリではなくファイルに年齢を格納します。
(ここは本当はファイルではなくネットワークに接続する
Proxyパターンにするべきなのでしょうけど長くなると困りますからね。)

もしPersonのインターフェースを作った場合、
それを使ってIsAdult()を呼べるべきです。

しかしそうすると、もし拡張メソッドを使わなかったら、
全ての実装したクラスでも
Age >= 20;
とやらなければなりません。

新たにクラスを1つ作るたびに重複が多くなってしまいます。

拡張メソッドを使えばその問題が解決されます。
Age >= 20;
は一ヶ所、拡張メソッドの中に集中します。
すばらしい。

実際の.netライブラリではオブジェクトのコレクション(?)
を表すIEnumerableインターフェースが拡張されています。

IEnumerableはオブジェクトを列挙する能力しかありませんが、
System.Linq.Enumerableによってさまざまな便利な能力を
付与されています。

拍手[1回]


"Orcas"とC# 3.0 その1 自動実装プロパティ

遅ればせながら次期Visual Studio、
"Orcas" Express Editionをインストールしてあそんでみました。

インストールしてさっそく起動してみると、
・・・特に2005と変わらないように見えます。
orcasStartPage.JPG
しいて違うところをあげるならタブの色がよりLunaっぽく、
かっこよくなっているということくらいでしょうか。

とりあえずプロジェクトを作って何かやってみることにしました。
orcasNewProject.JPG
当たり前ですが作れるプロジェクトの種類が2005より少ないようですね。
ともかく、一番単純な、コンソールプログラムで遊ぶことにしました。

今回の目玉はLinqらしいので
おいしいものは最後に取っておくと言う意味で
まずは地味な機能を使うことにします。
なんでもC# 3.0ではプロパティの宣言が
簡単になったとの事ですので。(自動プロパティ)

using System; struct Vector2 { public float X { get; set; } public float Y { get; set; } } class Program { static void Main(string[] args) { Vector2 vector = new Vector2(); vector.X = 0; vector.Y = 1; Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }

結果はこうです:

x = 0, y = 1

Rubyのように、プロパティを宣言するだけで、
変数の宣言の必要は無いらしいです。
それにしてもこのプロパティの書き方はインターフェースで
プロパティを宣言するやり方に似ています。(伏線)

次にコンストラクタを書いてみました。
わざわざ2行使ってプロパティに値を代入するのは
面倒に思えたからです。
しかし、失敗しました

Compile Error!

using System; struct Vector2 { public float X { get; set; } public float Y { get; set; } public Vector2(float x, float y) { this.X = x; this.Y = y; } } class Program { static void Main(string[] args) { Vector2 vector = new Vector2(0, 1); Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }
"The 'this' object cannot be used before all of its fields are assigned to"
とでました。他にもそれに関連したエラーが2つ。
どうやらコンストラクタ中でプロパティに代入してはいけないようです。

「だったら一体どうやって初期化するんだ」という気になります。
調べてみると、オブジェクトを初期化する特別な方法も出たそうで、
それを使えばよさそうです。
using System; struct Vector2 { public float X { get; set; } public float Y { get; set; } } class Program { static void Main(string[] args) { Vector2 vector = new Vector2 { X = 0, Y = 1 }; Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }
すばらしい・・・。

ものすごく簡潔です。
引数をとるコンストラクタを書く必要がありません。

もしかするとWindows Presentation Foundation(WPF)で
引数をコンストラクタにもつクラスが少なかったのは
これへの布石だったのかもしれません。
単に怠慢でコンストラクタを用意しなかったのではなく、
必要なかったから用意しなかっただけなのかもしれませんね。
引数(?)の意味もより明確になっています。

なんとなくCωの初期化の仕方を思い出します。

次に気になるのは「読み取り専用のプロパティを作れるのかどうか」
ということです。

そこで確かめるために次のようなコードを書いてみましたが、
失敗です。

Compile Error!
using System; struct Vector2 { public float X { get; } public float Y { get; } public static Vector2 Create(float x, float y) { return new Vector2 { X = x, Y = y }; } } class Program { static void Main(string[] args) { Vector2 vector = Vector2.Create(0, 1); Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }
"'Vector2.X.get' must declare a body because it is not marked abstract or extern.
 Automatically implemented properties must define both get and set accessors."

・・・だそうです。どうもabstractやexternのプロパティと勘違いされたようです。
実際書き方は同じですし。
と言うか書き方が同じと言うより意味的に同じなのかもしれません。
エラーメッセージによると、自動プロパティは
自動的に実装される」と言う意味らしいからです。

ネーミングの由来は納得しましたが
だからと言って問題が解決したわけではありません。
やはり調べてみると、盲点でした、setの前にprivateをつけるらしいです。
using System; struct Vector2 { public float X { get; private set; } public float Y { get; private set; } public static Vector2 Create(float x, float y) { return new Vector2 { X = x, Y = y }; } } class Program { static void Main(string[] args) { Vector2 vector = Vector2.Create(0, 1); Console.WriteLine("x = {0}, y = {1}", vector.X, vector.Y); } }
この方法はプロパティを手動で実装するときと同じですが、
その場合はたいていprotected set...となります。
まさかprivate setになるとは・・・完全に盲点でした。

しかしおかしな話です。
上のブログでも言われていますが、private setくらい
自動的に補完して欲しいものです。
setできないのならプロパティに意味なんて無いわけですから。
(そしてこの疑問に対する回答もやはり要領を得ません。)

将来は改善されるといいのですが・・・

拍手[0回]


        
  • 1
  • 2