今回の記事では拡張メソッド、Linq、属性、クラスの部分定義など開発にとても役立つC#の機能を説明します。
前回のデリゲートとイベントはちょっと難易度が高かったですが、今回の記事はもう少し取り組みやすくなります。ぜひ読んでみてください。
前回の記事:
C#の便利な応用機能を紹介
ここまでの記事でC#の基本的な機能については大体解説し終えました。
今回の記事では次の機能について解説していきます。
- Linq
- 属性(Attribute)
- 拡張メソッド
- クラスの部分定義
Linqとは
Linqとはコレクションから要素を取り出すクエリ(Query)操作を行うC#の機能になります。対応するコレクションにはXMLやSystem.IEnumerableを継承したクラスなどになります。
Linqという名前はLanguage-Integrated Query (LINQ)から来ています。
クエリ操作にはデータベースのクエリ構文と似たようなキーワードを使用します。そのためコレクションの要素を簡単に取り出すことができ、さらにループ文で書くより読みやすく理解しやすいものとなります。
そのため、コレクションから要素を取り出したい時は基本的にLinqを利用するといいでしょう!
Linqはかなり多機能な機能になりますのでここでは簡単に紹介するだけに留めておきます。
また、Linqはキーワード形式とメソッド形式の二通りの書き方があります。
キーワード形式では主要なLinqの機能だけ使用できます。そのためLinqの全ての機能を使用できませんので注意してください。
メソッド形式はメソッドをつなげて処理を書いていくことからメソッドチェーンと呼ばれたりします。また、メソッド形式では処理をラムダ関数で定義すると便利です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using System.Linq; using System.Collections.Generic; int[] list = new int[] {0, 1, 2, 3, 4, 5 }; {//キーワードを使ったもの //queryにはlistの中の0, 2, 4にアクセスできる。 IEnumerable<int> query = from value in list where 0 == (value % 2) select value; } {//メソッド形式 //queryにはlistの中の0, 2, 4にアクセスできる。 IEnumerable<int> query = list.Where(_v => 0 == (_v % 2)); } |
Linqのキーワードについて
Linqでは以下のキーワードが用意されています。これらを組み合わせてコレクションから要素を取り出したりできます。
キーワード | メソッド形式 | 概要 |
from | なし | コレクションから取り出す要素の名前を指定する。 |
where | Where() | 条件式で要素をフィルタリングする。trueならその要素を使用する。 |
select | Select() | クエリ操作の結果最終的に使用する値を指定する。 |
group | GroupBy() | 要素を条件式でグループ化する。 |
order | OrderBy | 要素をソートする。 |
join | Join(),GroupJoin() | 2つのコレクションを組み合わせてクエリしたい時に使用する。 |
let | なし | クエリ式の中で使用する一時的な変数を定義する。 |
(※実際にはこれ以外のキーワードも使用していますが、主要なものだけ紹介しています。)
(※メソッド形式で「なし」のものは、C#の文法で代替できます。)
Linqのサンプル
Linqはコレクション操作を簡単に行うためのものですが、一部のキーワードについては使い方がわかりづらいものもあります。ここでは簡単なサンプルを用意したので参考にしてください。
基本形
1 2 3 4 5 6 7 8 9 10 11 |
using System.Linq; var list = new List<int>() { 0, 1, 2, 3, 4, 5 }; // 次のクエリ文ではqueryに {3, 4, 5}が入る var query = from num in list // <- listの要素をnumという名前で操作していく where num > 2 // <- numが2より大きいものをフィルタリングする select num; // <- numをクエリの出力として使用する //メソッド形式では次のようになる var query2 = list.Where(num => num > 2); |
Group
コレクションの要素を指定した条件式でまとめるクエリ文になります。
- キーワード形式:
group <使用する要素の値> by <条件式>
- メソッド形式:
GroupBy(<条件式>, <グループ化時に使用する値のセレクタ>)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System.Linq; var list = new List<int> { 0, 1, 2, 3, 4, 5 }; //listの要素を偶数か奇数かでグループ化する。 var query = from num in list //numを使用してグループ化する // byキーワードの後にグループ化の条件式を書く。 group num by 0 == (num % 2); //メソッド形式 var query2 = list.GroupBy( num => 0 == (num % 2), // <- グループ化の条件式 num => num); //<- 要素の値をそのまま使用する //クエリ結果を使用する例 foreach(var group in query) { group.Key;// <- グループ化の時に使用した条件式の結果が入る。 //グループ化した値を見る時はgroupをforeachに渡す foreach(var num in group) { } } |
Join
2つのコレクションの要素を見て、指定した条件式が一致するものをまとめる時に使用するクエリ文になります。
Joinには通常のものとinto文を追加したものがあります。
- 通常のもの:コレクションの要素を1対1でまとめます。
- into文:コレクションの要素を1対多と片方のものをグループ化してまとめます。
書き方は次のようになります。
- 通常のもの:
join <要素名> in <コレクション> on <既に見ているコレクションの要素の値> equals <要素名の値>
- into文:
join <要素名> in <コレクション> on <既に見ているコレクションの要素の値> equals <要素名の値> into <グループ化した時の名前>
メソッド形式では次のものがあります。
- 通常のもの:
Join(<組み合わせるコレクション>, <<既存のコレクションの要素の値>, <組み合わせるコレクションの要素の値>, <ペアにする時のセレクタ>)
- into文:
GroupJoin(<組み合わせるコレクション>, <既存のコレクションの要素の値>, <組み合わせるコレクションの要素の値>, <グループ化時に使用する値のセレクタ>)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
using System.Linq; //Houseに住んでいるPersonをLinqで調べるサンプルコード class Person { public int ID { get; set; } public string Name { get; set; } } class House { public int PersonID { get; set; } public string Address { get; set; } } var personList = new List<Person>() { new Person() { ID = 0, Name = "Tom" }, new Person() { ID = 0, Name = "Kumi" }, new Person() { ID = 0, Name = "Mei" }, new Person() { ID = 1, Name = "Ken" }, new Person() { ID = 1, Name = "Sara" }, }; var houseList = new List<House>() { new House() { PersonID = 0, Address = "Apple" }, new House() { PersonID = 1, Address = "Microsoft" }, }; //同じHouseに住んでいるPersonを調べるクエリ文 var query = from house in houseList // <- houseListの要素をhouseに設定する //join文 personListも一緒に調べている // personListの要素をpersonに設定していて、 // house.PersonIDとperson.IDが同じpersonをpersonGroupにまとめている join person in personList on house.PersonID equals person.ID into personGroup // クエリ結果を指定している。 select new {House: house, PersonGroup: personGroup}; //メソッド形式 var query2 = houseList .GroupJoin(personList, //<- 組み合わせるコレクション。 house => house.PersonID, //<- houseListの要素をそのまま使用している。 person => person.ID, //<-personListの要素をそのまま使用している。 //第2,3引数の値が一致するものが引数として渡される。 // houseはhouseListの要素が、personGroupには一致したpersonListの要素がまとめられている。 (house, personGroup) => (House: house, PersonGroup: personGroup)); //クエリ結果を使用する例 //queryには次のような感じのデータが入っています。 // {houseList[0], { personList[0], personList[1], personList[2] } } // {houseList[1], { personList[3], personList[4] } } foreach(var pair in query) { // pair.House // <-House // pair.PersonGroup // <- HouseにいるPersonのコレクション } |
属性(Attribute)とは
属性(Attribute)はクラスおよびメンバなどのC#の要素に対して付加的な情報(メタデータ、Metadata)を指定できる機能になります。これまでの記事でも既にいくつか使用しています。
書き方としてはクラス・メンバ宣言の前に角括弧[]
で括った中に指定したい属性を書いていきます。
書き方:[<属性名>( <引数>... )]
属性にはその定義の際に指定できる種類が設定されています。指定できる種類は大体のC#の要素になります。
- クラス
- フィールド
- メソッド
- プロパティ
- イベント
- 列挙型
UnityやC#には既に定義されている属性がありますのでそちらを利用することもできます。これらの属性を使用するとその属性に対応しているクラスで何かしらの恩恵を受けたり、UnityではInspector上のUIが変更されたりします。
- RangeAttribute:指定したものが数値型の場合、Inspector上では指定した範囲内でしか値を変更できなくする。
- MinAttribute:指定したものが数値型の場合、Inspector上では指定した値より下回らないようにする。
- SerializeFieldAttribute:指定したメンバをUnityのシリアライズ(オブジェクトをデータ出力してファイル保存することをシリアライズという)対象にする。Inpectorでも編集できるようにする時に使用する。
などなど。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using UnityEngine; public Sample : MonoBehaviour { //属性はクラス定義やメンバ定義の前に書く [Range(0f, 100f)] public float Value; //複数指定することも可能 [SerializeField] [Min(-10)] int _int; //下のように,で区切ってもOK //[SerializeField, Min(-10)] // int _value2; // ... } |
新しい属性を定義する時の注意点
新しい属性をこちらから定義することももちろん可能です。
定義する際はSystem.Attributeを継承してください。
その際定義する属性の情報を指定する必要があり、それにはSystem.AttributeUsageAttribute属性を使用してください。
AttributeUsageAttribute
に渡すことができるパラメータは次のものです。
- 第1引数:指定できる対象についてのフラグ(System.AttributeTargets)
- Inherited::派生した先でも影響を与えるかどうか?
- AllowMultiple:複数指定できるかどうか?
1 2 3 4 5 6 7 |
using System; [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] public class MyAttribute : Attribute { public int Prop { get; set; } public MyAttribute(int prop) { ... } } |
属性を定義するだけならクラス定義とほぼ同じですが、それを利用するアプリを開発したい時はリフレクションという型情報を調べるC#の機能を利用する必要があります。
リフレクションの機能についてはまた別の記事で解説していきたいと思います。
拡張メソッドとは
拡張メソッドは既に定義されたクラス・構造体に対してメソッドを追加定義する機能になります。
拡張メソッドを利用することで既にあるクラスの機能を拡張できたり、よく使う処理をまとめたりすることができます。継承を使わずに元あるクラスに機能追加できるのがミソですね。
拡張メソッドを定義する時は必ず、以下の条件を守る必要があります。
- static指定されたクラスの中で定義する。
- 静的メソッドとして定義する。
- 第一引数には「
this <型名> <引数名>
」と初めにthisキーワードを書く必要がある。
拡張メソッドは拡張対象のクラス・構造体のメンバではありません。そのため、使用できるメンバはpublicなど他のクラスからでも使用できるメンバのみになるので注意してください(privateメンバにアクセスできない)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class A { public int Value; } //拡張メソッドはstatic指定されたクラスの中のみ定義できる。 public static class Extensions { //拡張メソッドは静的なメソッドとして定義するので、 // 対象となるクラスからアクセスできるメンバのみ使用できる。 public static int GetValue(this A inst) { return inst.Value; } } var inst = new A(); inst.Value = 100; var n = inst.GetValue(); // <- n == 100 |
クラスの部分定義とは
クラスの部分定義はクラス定義を複数のクラスで行う時に使用する機能になります。主にC#のFormアプリケーションを作る時に使用されています。
クラスの部分定義を使用したい時は必ずpartialキーワードをクラスに指定する必要があります。
書き方:partial class <クラス名>
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//クラスの部分定義を行いたいクラスには必ずpartialを指定する。 partial class A { public int Value; } //クラスの部分定義は他のファイルにあってもできる。 partial class A { public int AddValue(int add) => Value + add; } var inst = new A(); inst.Value = 100; var n = inst.AddValue(2); // <- n == 102 |
まとめ
今回の記事ではC#の便利な機能についてみてきました。
簡単にまとめますと以下のようになります。
- Linqはコレクションに対するクエリ(Query)操作のこと
- Linqを使うことでコレクションの要素をデータベース的に検索することができる。
- 属性はクラスやメンバにつけることができる付加的な情報を表す。
- 属性を利用したプログラムを作る時はリフレクションという型情報を調べるC#の機能を利用する必要がある。
- 拡張メソッドはあるクラスにメソッドを追加するC#の機能
- 拡張メソッドを定義するクラスはstaticを指定する必要がある。
- 拡張メソッドから使用できるクラスのメンバは静的メソッドからアクセスできるもののみになる。
- クラスの部分定義はクラスの定義を2つ以上の場所で行うことができる機能。
- クラスの部分定義では複数のファイルで行ってもいい。
- クラスの部分定義を行いたい時はクラスに必ずpartialを指定する必要がある。
それでは次の記事に行ってみましょう!
コメント