C#のデリゲート周りについてまとめておく
皆さんこんにちはぐっちーです。
緊急事態宣言下で私は4/29~5/11の期間がGWになって最初のほうがウハウハだったのですが外へ出てもお店の大半は営業していないので結局家から出ることなくゲームするか寝るかしかない生活を送っております。
そんな状態なので積読の消化を始めたんですがEffectiveC# 6.0/7.0という本を読んでる中でデリゲート周りのもやもやが晴れた気がするので忘れないうちに記事にしておきたいと思います。
はじめに
私はこれまでデリゲートがどんなものかくらいには知っていたんですがイベントとの違いを述べろだとかLinqなどでよく出てくるFuncやActionといったものが何なのかイマイチわかっていなかったのでそれらを使うことは基本的に避けていました。それが今回なんとなく解消されたというお話です。
デリゲートとは
デリゲートとは簡単に行ってしまえばメソッドを参照するための型です。そう型なのです。
型なので使用する際にはクラスや構造体と同じように変数を用意してそれに対してこのメソッドを参照してくれみたいに使います。
デリゲート型の変数を定義するには以下のようにメソッドの返り値と引数を指定します
delegate 返り値 型名(引数のリスト) 例 delegate void MyDelegate(int a);
そしてこのように定義したデリゲートにメソッドを登録したりデリゲートを介してそれらのメソッドを呼び出す場合には以下のように記述します。(クラスの前にデリゲートの定義があると思ってください)
class MyClass{ static void Main(){ //MyDelegate型の変数を用意してメソッドを追加 Mydelegate del = Method1; del += Method2; del += Method3; del(3); } static void Method1(int a) {Console.WriteLine($"入力された数字は{a}です")}; static void Method2(int a) {Console.WriteLine($"ニュウリョクサレタスウジハ{a}デス")}; static void Method3(int a) {Console.WriteLine($"Input is {a}.")}; }
出力は以下の通りになります。
入力された数字は3です ニュウリョクサレタスウジハ3デス Input is 3.
さらっとコードに登場しましたがデリゲートには複数のメソッドを追加できます。デリゲート型のオブジェクトは参照するメソッドのリストを持っているのでそれを順に実行していくことになります。 また、追加するメソッドはstaticメソッドの他にもインスタンスメソッド(非staticメソッド)も追加できます。細かい使い方については以下のサイトに非常に丁寧に記載されているのでそちらを参照してください。 ufcpp.net
Predicate・Action・Func
C#にはPredicate、Action、Funcと呼ばれるものが存在します。これらは何なんでしょうか。それぞれが別々の役割を持ちますが共通して言えることはどれもジェネリック型を引数や返り値に使用するデリゲート型であるということです。
Predicate
Predicate
つまるところ以下のように定義しても同じものができます。
delegate Boolean MyPredicate<T>(T val);
けどまあ事前に用意されているのであればわざわざ用意する必要がないのでPredicate型のデリゲートを使えばいいと思います。
List
Action
Actionは任意の個数の引数を取り返り値がない(返り値がvoid)であるデリゲート型です。自分で定義するとこんな感じです。
delegate void MyAction<T>(T val);
Action型や後述するFunc型は引数を取らない場合もあります詳しくは公式のリファレンスなどを参照してください
Func
Funcは任意の個数の引数を取り返り値があるデリゲート型です。実装するとこんな感じ
delegate TResult MyFunc<T>(T, TResult)
TResult型についてはジェネリックな返り値の型だと考えてもらえればいいと思います。
LINQで出てくるメソッドはほぼ引数がFunc型です。LINQを使っている方でこう思う人がいるかも知れません
「いや、引数部分にはラムダ式を書いているのであってデリゲートなんてものは使ってないよ」と
そのラムダ式ですが実のところ記述することでデリゲートを生成しているのです。そもそもラムダ式とは匿名関数と呼ばれるものの一種であり、その匿名関数を用いることでインラインでデリゲート型のインスタンスを生成できるようになるのです。
そのためLINQの場合ではメソッドの引数部分にラムダ式を記述することでFunc型のデリゲートを生成して渡しているといった処理になるわけです。
event
デリゲートに似ているといえばeventですね。デリゲートが直接絡んでくるのはeventではなくてEventHandlerの方ではあるのですがそのあたりもまとめて話していきます。
まずeventというのはデリゲート型のプロパティの一種です。プロパティと何が違うかというとeventキーワードを付けた場合そのデリゲートに対して外部からはメソッドの登録と解除しかできなくなります。
public class MyEventArgs{ public int a; public int b; public MyEventArgs(int i, int j) { a = i; b = j;} } public class MyClass{ //通常のプロパティ private Action<object, MyEventArgs> m_myDelegate; public Action<object, MyEventArgs> MyDelegateProp { get{ return m_myDelegate; } set{ m_myDelegate = value; } } //イベント private event Action<object, MyActionArgs> m_myEventHandler; public event Action<object, MyActionArgs> MyEventHandler{ add { m_myEventHandler += value; } remove { m_myEventHandler -= value; } } }
こんな感じであったときにできる操作とできない操作の比較は以下のようになります。
public void Hoge(MyClass source){ //プロパティへの操作(全部できる) source.MyDelegateProp = this.Method1;//メソッドの設定 source.MyDelegateProp += this.Method1;//メソッドの追加 source.MyDelegateProp -= this.Method1; //メソッドの解除 source.MyDelegateProp = null; //全解除 source.MyDelegateProp(this, new MyEventArgs(1,2)); //デリゲートの呼び出しf //イベントへの操作 source.MyEventHandler += this.Method1; //メソッドの追加(できる) source.MyEventHandler -= this.Method2; //メソッドの解除(できる) //source.MyEventHandler = this.Method1; メソッドの設定(できない) //source.MyEventHandler = null; 全解除(できない) //source.MyEventHandler(this, new MyEventArgs(1,2)); デリゲートの呼び出し(できない) }
eventを使う場合それに登録されたメソッドの実行はeventが宣言されているクラス(上の例だとMyClass)内でしかできません。MyClass内ではプロパティ同様に使用することができます。
なのでObserverパターンのような使い方をしたい場合はEventを使ってメソッドを述語として使用したい場合は普通のプロパティを使えばいい感じです。
ざっくりまとめる
では最後に今回のまとめのまとめ(?)をしていきます。
- デリゲートはメソッドへの参照を持たせるための型
- Predicate・Action・Funcなどは事前に用意されたデリゲート型の一種
- eventはデリゲート型のプロパティの一種で安全性が高い
こんな感じでしょうか。もう少し込み入った話は参考にした記事を貼っておきますのでそちらを見ていただければと思います。
個人的にだいぶこのあたりについて知識が深まった感じがするので改めてLINQとかを学び直そうかと思います。
それでは今日はこのへんで( ー`дー´)キリッ
参考記事
新人プログラマーとしてゲーム会社に入社して1ヶ月が経った件について
皆さんどうもこんにちはぐっちーです。
モンハンライズが発売されてから開いてる時間の殆どをそれに費やしている私です。
さて今回の記事では4月1日から新生活をはじめて明日で1ヶ月ということで、最初くらいは真面目にどんな感じかの報告を書こうかと思った次第です。それではいってみよう!!
入社前(3月終わり頃)
「え?そこから書くの?」と思われる方おられるかと思いますがぶっちゃけた話、社内でどんなことしてるのかを詳細に書くわけにはいかないのです。でもそれをなしにするとブログがものすごい短くなってしまうので引っ越してから入社前までの生活を入れることで程よいボリュームに仕上げようという算段です。(正直に喋っていくスタイル)
私の地元や大学生活を送っていた土地はお世辞にも都会とは言い難いというかど田舎であるわけなんですが、そんなところから突然大阪という日本有数の大都会に引っ越してきた私は驚きの連続でした。
- 電車の本数が多い
- 15~20分圏内にだいたいなんでもある
- 車必須の社会ではない
- UberEats
都会での生活になれている方ならそんなの当たり前だとおっしゃるかもしれませんが22年間田舎生活だった私からすればどれも驚くことなのです。中学の同級生が大阪の大学に進学していて引っ越してすぐくらいに軽く案内してもらったんですが、「ここまでうちから自転車で20分くらいだから普通に来れるわ」と伝えたところ「いや、電車使えばいいじゃん。なんで自転車換算なの」とツッコミをもらいましたwww どちらで行くかはさておいて最初に自転車換算が出てしまうあたり車を持っていない田舎者の感覚なんでしょうなぁ...
いくつか観光したときの写真を載せておきます。
私はかなりの酒好きなんですが本場大阪の串カツはめちゃくちゃ美味しいし酒に完璧に合いました。
こんな感じで入社前はコロナに気をつけつつ大阪観光を満喫していました。
入社・研修
4月1日が入社式でその日から研修が始まりました。私の務める会社は基本的には出社して仕事をしているので研修も出社して行うような感じでした。
よくあるビジネスマナーや会社についての研修がだいたい2週間位で専門的な研修がその後続いていくような感じです。詳しい内容はお伝えできませんが現在はその専門的な研修をしています。
そんな中で感じるのは周りがみんなすごい技術力が高いということです。コーディングやその他技術的な話をしていてもすごいレベルが高いことをしているなと感じました。同期といえどここはもう学校ではなく会社なので個人の能力は評価され給与にも響いてきます。遅れを取らないように頑張らないとなぁと改めて痛感させられました。
あとは本当の現場の雰囲気ですね。一昨年の夏に別の会社ではありますが1ヶ月就業形インターンに参加していたのでゲームの開発現場の雰囲気に触れるのは初めてでは無いのですがあのときはあくまでインターン生という感じだったので一歩踏み込んだところまでは感じていなかったんですがいざ社会人としてその現場に入るとその時感じたものとはより違ったと言うか強まったという感じの雰囲気を感じました。
その他には研修の中でコードレビューの機会もあって仕事していく上で気をつけるべき書き方なんかもたくさん教わりました。
今後
まだしばらく研修は続きますがいずれプロジェクトに配属になると思います。そのときに前線で働ける人材になる...にはまだまだ時間が足りないと思います。ですが一人のプログラマとしてまっとうに与えられた仕事をし、その上で付加価値をつけて貢献できるくらいの人材にはなっていたいと思っているのでしっかり努力して5年後、10年後にはチームを引っ張るくらいの役につけたらなぁと思います。
こんな感じで私のゲームプログラマーとしてのスタートが切られたよというお話でした。
もともと投稿頻度の低い当ブログですが今後勉強することは多いと思うので新しい技術だとかデザインパターンみたいな考え方だとかを学んだら備忘録がてら時間を見つけて記事にしていきたいです。
それでは今日はこのへんで( ー`дー´)キリッ
会津大学を卒業しました
みなさんこんにちは。ぐっちーです。
タイトルにもある通りなんですが本日会津大学にて学位授与式が執り行われまして無事コンピュータ理工学部を卒業いたしました。
式はコロナウイルス感染拡大防止のために卒業生と先生方のみの参列で行われたのでだいぶあっさりと終わりましたwww
今回の記事では長いようで短かった私の大学生活について振り返りをしていきたいなと思います。
1年次
高校生活を終えて実家からも遠く離れた土地で一人暮らしを始めた私としては初年度は新しいことの連続で4年間の中で一番内容の濃い1年だったと思います。高校よりも数段レベルの上がった数学や科学系の授業や教養系の授業に始まり、一人暮らしをしてると出てくる諸々などで非常にワタワタと生活を送っていた気がします。その中で一番楽しんでいられたのはやはりA-PxL(当時は会津大VR部でした)での活動かなと思います。私は工業高校の情報科出身だったのでブログラミングの基礎的なところは割と理解していたのですが何か開発したことがあったかというとそうではなかったのでVR部でUnityを使ってゲームを作るという経験が非常に刺激的でもしかしたら学校の授業以上に熱心に勉強していたかもしれませんwww
また、私は高校が電車で20分くらいのところに通っていたので学校の友達と家で遊んだりだとか帰りにゲーセンやカラオケに遊びには行っても夜までみたいなことは距離の関係上無理だったので大学の仲間と夜通しゲームしたりしたのはある意味初めてで楽しかった思い出もあります。
2年次
2年の時はIVRC2018というVRの学生コンテストに参加しました。自分たちのチームは同学年のメンバー3人プラス私の4人で参加したのですがなんとまさかの全員が開発初心者という前代未聞のチームでした。一応企画書通りの体験ができるようなコンテンツには仕上がったのですが今思うともう少しよくできたと思う点はあります。また、当日も展示することに慣れていない私たちなのでVIVEのベースステーションが干渉しまくってしまいまともにコンテンツを体験してもらえなかったのは反省すべき点だと今でも思っています。しかしアイデアやコンテンツの研究性(?)が評価され出場したユース部門にて銀賞をいただくことができました。表彰式でチーム名がよばれた時はチーム全員がそれまでの人生一びっくりしていましたwww
3年次
3年からは研究室に配属になり授業も専門科目がほとんどを占めるようになっていっそう専門的な学習をするようになりました。会津大学は基礎的な内容は2年生まででソフトウェア・ハードウェア両方について学ぶのですが3年生以降は所属した研究室の分野に沿った授業を取ることが多いです。
自分の研究室はコンピュータと芸術を組み合わせた物がテーマとしているのでCGや幾何学など芸術系の単位を取ることが多かったです。また、研究室の定例会やUniteへの参加などでより多くの専門的な知識を学んでいました。
あとは就活の記憶しかないです。5月ごろから募集が始まっているインターンにとりあえずたくさん申し込んで会社の雰囲気だとか現場で働く人たちの技術なんかに触れて就職先を探している感じでした。3月にはカヤック様にて、9月にはサイバーエージェント様で就業型インターンを経験し部活内や個人開発では得られない貴重な経験をさせていただきました。
主にゲーム業界で自分のスタンスと合いそうなところを探して10月ごろから本選考を色々な会社を受けていた感じでした。なかなか決まらず4年の最初にかけてだいぶ精神的に参ってしまっていました。
4年次
あっという間の大学生活最終年度ですが4月の時点では内定0でしたので卒論よりも就活の方に本腰を入れている感じでした。この辺りでは単位も撮り終わっていたのである時間全てを割くことができていました。無事内定が出たのは6月ごろでそこからは割とポジティブに物を考えるようになるくらい就活が与えていた影響って大きいんだと思いました。
就活も終わって卒論に手をつけ始めたのは6月ごろ私は入学当初からVR空間を無限に歩行することについて興味があったのでその辺りの技術について情報収集から始めました。そんな中目についたのが「リダイレクテッド・ウォーキング」という技術で実際の動きと少し異なる映像をVR空間内で見せることで限られたスペースを移動し続けているのにもかかわらずVR空間内では直進し続けたり、実空間でのスペースよりも広いスペースを移動できるようになるという技術です。詳しい話はA-PxLのブログの方に記載していますのでよろしければご覧ください。
私の卒業論文はこれを実装するという物でアイデア出しから実装に至るまで大体1~2ヶ月くらいでプロトタイプを作成しあとはデバッグして合計3ヶ月くらいで実装そのものは完了していたと思います。
ただ大変なのはここからで会津大学の卒業論文は全員全文を英語で書かなければならないのです。日本語の文章を書くことは割となんとかなるのですが論文となると話は別でさらに英語なので適切な表現などを調べながら英文にして書くのがものすごく大変でした。私の研究室の教授は外国の方なのでカジュアルな場面とフォーマルな場面での文法の使い分けやカタカナ化されている英単語と本来使うべき英単語のニュアンスの違いなどを教えていただくこともできたので英語論文の執筆はすごい経験になったと思います。
最後に
記事にするためだいぶ割愛はしていますが新しいことだらけで4年間あらゆる面で学びの多い大学生活だったと思います。
また、たくさんの方と関わらせていただいてたくさんの人にお世話になりました。そのみなさんにこの場でお礼を伝えさせていただきたいと思います。ありがとうございました。
4月から新社会人、新米ゲームプログラマとして大阪の地にて頑張っていこうと思います。Twitterの主な生息地として今までみたいに気が向いたタイミングで何かしらの記事を当ブログにてあげていきたいと思いますのでこれからも見ていただけると幸いです。
それでは今日はこの辺で(`・ω・´)
Stateパターンで状態管理はじめました
どうも皆さんこんにちはぐっちーです。
ブログでは昨年末ぶりですね、あけましておめでとうございますww(なお2月)
新年明けてからは卒論の最終段階に入ったり、研究室全体で出席した学会用の論文の校正をしたり新生活に向けての手続きやらでいつの間にか2月になっていました。
2月になったら落ち着くわけもなく卒論発表が半ばにあったり引っ越しがあったりするので結局忙しいのです(´・ω・`)。
しかしまあ長いようで割とあっという間だった大学生活最後の2ヶ月を謳歌していきたいと思っていますww
さて、今日の本題にはいる前にお知らせが一つあります。当ブログのヘッダー画像を更新いたしました。
私のアカウント全般のサムネにもなっている私のVRChatアバターですがこの度絵師の方に依頼してちびキャラにしていただきましたのでそちらを使用したヘッダー画像となっています。私の要望通り完璧なイラストを描いていただいたこずえさんには感謝しかありません。この場を使ってお礼申し上げます。
イラストを描いていただいたこずえさんのツイッター↓
twitter.com
それでは本編
ではでは本編に入っていきたいと思います。今日はタイトルにもある通りStateパターンについて話していきたいと思います。今回の記事はどちらかというと私が学んだ内容を書き示しておこうみたいな記事にする予定です。現在制作中の自作ゲーでStateパターンを用いて状態管理をするようになったのでどんなふうにしたのかをある程度簡略化して紹介していきたいと思います。
状態管理
Stateパターンの話に入る前に状態の管理について考えてみましょう。ゲームを例に取ってみると、ゲームの状態はざっくり分けて「スタート画面」「ゲーム本編」「リザルト画面」に分かれると思います。(ゲームの種類によってはこの限りではありませんがわかりやすくするためにこの記事ではこの3つで話します。)そしてゲームを作っていく中で各状態でしたいことは変わってくると思います。当然現在どの状態であるかを判断して適切な動作をするような実装が求められます。かんたんな実装ではifを用いた分岐でしょうか。
if文で状態管理(クリックで展開されます)
string currentState; if(currentState == "Start"){ ------スタート画面の処理----- } else if(currentState == "Play"){ ------ゲーム本編の処理----- } else if(currentState == "Result"){ ------リザルト画面の処理----- }
私自身もこのようにこれまでこのように書いていました。しかしこの方法では状態が増えるごとに条件分岐全体を見直す必要が出てきます。今でこそ短いコードですがコメント部分を実際に実装するとなると結構なコード量になると思います。そのためあまりこの方法は好ましくありません。そこでStateパターンというものを導入します。
Stateパターン
StateパターンとはGoFのデザインパターンの一つで状態をそれぞれクラスとして、それらを切り替えることでオブジェクトの状態を表現する方法のことです。
Stateパターンは各状態を分けて実装するのでスタート画面での振る舞いを実装しているときには本編の振る舞いを木にする必要はありません。よく「分割して統治せよ」と言われるようになるべく細分化して置くことであらゆる点で有効に働きます。
以下にStateパターンのクラス図を示します。
属性や操作についてはこの後にコードを載せるのでそれを見ていただければいいですが、かんたんに説明をすると状態を管理するStateManagerクラスがStateインターフェースを実装した各状態を表すクラスに対して操作を行うのですがStateManager側の処理はStateで定義されている関数を呼ぶに過ぎないのでそれぞれの状態でどのような振る舞いをするかは状態を表すクラスに一任されているという点がポイントです。
続いてコードの方に移ります。
State (状態を示すインターフェース)
public interface State{ public void OnUpdate(); //毎フレーム呼ばれる関数 public void OnStateEnter(); //状態に入ったときに呼ばれる関数 public void OnStateExit(); //状態を出るときに呼ばれる関数 }
Start (スタート画面)
public class Start: State{ private Start instance = new Start(); public Start GetInstance(){ return instance; } public void OnUpdate(){ -----スタート画面の処理----- } public void OnStateEnter(){ -----スタート画面開始時の処理----- } public void OnStateExit(){ -----スタート画面終了時の処理----- } public string GetStateName(){ return "Start" } }
Play (ゲーム本編)
public class Play: State{ private Play instance = new Play(); public Play GetInstance(){ return instance; } public void OnUpdate(){ -----ゲーム本編の処理----- } public void OnStateEnter(){ -----ゲーム本編開始時の処理----- } public void OnStateExit(){ -----ゲーム本編終了時の処理----- } public string GetStateName(){ return "Play" } }
Result (リザルト画面)
public class Result: State{ private Result instance = new Result(); public Result GetInstance(){ return instance; } public void OnUpdate(){ -----リザルト画面の処理----- } public void OnStateEnter(){ -----リザルト画面開始時の処理----- } Z public void OnStateExit(){ -----リザルト画面終了時の処理----- } public string GetStateName(){ return "Result" } }
StateManager (状態を管理するクラス)
public class StateManager{ private State currentState; private State nextState private Start startState; private Play playState; private Result resultState; public State CurrentState => currentState; private void Start(){ startState = Start.GetInstance(); playState = Play.GetInstance(); resultState = Result.GetInstance(); currentState = startState; } private void Update(){ if(nextState!=null){ currentState.OnStateExit(); currentState = nextState; nextState = null; currentState.OnStateEnter(); } currentState.OnUpdate(); } public void RequestNextState(State next){ nextState = next; } }
こんな感じで状態管理側からは各々がどのような振る舞いをするかはわかりませんがどの状態であっても同じ処理をすることができます。一応今回状態へ入るときと出るときに呼ばれる関数も用意しましたがこれが正解というわけでは無いのであしからず。なくても大丈夫だと思います。
今回のコードではGameManagerなどのクライアントからCurrentStateプロパティにアクセスして現在の状態を取得したりRequestNextState関数を呼んで状態の遷移を行えるようになっています。
状態ごとの振る舞いについてもクラスで分けているので変更を加えるときもわかりやすいですね。それでいて他に影響を及ぼさないのがいいです。
また、各状態は2つ以上存在することはありませんのでSingletonパターンを使ってインスタンスが一つであることを保証しておきます。(Singletonパターンについては他記事様を参考にしてください)
まとめ
ではではこの記事のまとめです。
- Stateパターンはオブジェクトの状態をクラスを用いて表現する方法である。
- Stateパターンを採用することで状態の各状態がクラスによって分割されるので保守性が向上する。
- インターフェースを使用したことで管理側は共通の処理で済むようになっている。
ゲーム開発では様々な場面で状態遷移が登場します。ゲームの規模が大きくなればそれらも非常に複雑化してくるので可能な限りわかりやすく管理したいですよね。今回これを実装してみて状態管理周りがif文を用いていたときに比べてだいぶスッキリしました。先人たちは素晴らしい方法を編み出したものだと思いますねwww
それでは今回はこのへんで( ー`дー´)キリッ