はじめに
C#言語はJAVAと似てるんでしょ
Unity 開発に使えるっていうし興味はあります
基本構文は特殊じゃないからいまさら学ぶことあるんですかね
C#はJAVAより新しい言語なの?
古い教材で学び直した私のノートをここに残します
教材と参考文献
-
一週間で身につくC#言語
-
Stringクラスで文字列操作総まとめ
-
テキスト「構造体」
-
【C#】構造体(struct)を完全に理解する
-
【C#】構造体の使い方
-
コレクション
-
演算子のオーバーロード
-
テキスト「ジェネリクス」
-
LINQ
◆ 開発環境
◎ プログラム言語
言語名 | 特徴 |
---|---|
C | 現在用いられている主流の言語の中では最も古い言語。多くの言語が、C言語をベースに作られている。 |
C++ | C言語を更に拡張した言語。オブジェクト指向といった考え方に対応。 |
Java | C/C++をベースにして、SunMicrosystemsによって開発された、Androidなどで用いられている言語。 |
C# | Microsoft社がJavaなどをベースに独自に開発した言語。 |
Objective-c | Apple社がC言語を独自に拡張した言語。iPhoneやiPadのアプリ開発に用いられる。 |
◎ コンパイラとインタプリタ
コンパイラ:一括翻訳して実行させる
インタプリタ:一部分ずつ翻訳しながら実行させる
◎ C# === C++++
C#はコンパイラで.NET Framework
という実行環境(フレームワーク)で動作する中間言語(CLI
:Common Language Infrastructure
:共通基盤言語)に変換し、その中間言語をインタプリタで変換しながら逐次実行している。
これにより、.NET Framework
が存在する環境なら機種やOSの違いに拠らず利用可能。
◎ Visual Studioで実行
Visual Studioでコンソールアプリを実行するには、
Ctrl + F5 キーを押して、デバッグなしでプログラムを実行。
◆ データ型
データ型 | 説明 | ビット | 範囲 |
---|---|---|---|
byte | 符号なし整数 | 8 | 0 ~ 255 |
sbyte | 符号付き整数 | 8 | -128 ~ 127 |
int | 符号付き整数 | 32 | -2,147,483,648 ~ 2,147,483,647 |
uint | 符号なし整数 | 32 | 0 ~ 4294967295 |
short | 符号付き整数 | 16 | -32,768 ~ 32,767 |
ushort | 符号なし整数 | 16 | 0 ~ 65535 |
long | 符号付き整数 | 64 | -922337203685477508 ~ 922337203685477507 |
ulong | 符号なし整数 | 64 | 0 ~ 18446744073709551615 |
float | 単精度浮動小数点型 | 32 | -3.402823e38 ~ 3.402823e38 |
double | 倍精度浮動小数点型 | 64 | -1.79769313486232e308 ~ 1.79769313486232e308 |
char | 単一 Unicode 文字 | 16 | テキストで使用される Unicode 記号 |
bool | 論理ブール型 | 8 | True または False |
object | 他のすべての型の基本型 | - | - |
string | 文字列 | - | - |
decimal | 29の有効桁数で10進数を表現 できる正確な小数または整数型 |
128 | ±1.0×10^28 ~ ±7.9×10^28 |
◆ 基礎
◎ コンソール入出力
Console.Write("input name > ");
string name = Console.ReadLine();
Console.Write("input number > ");
int num = int.Parse(Console.ReadLine());
Console.WriteLine("こんにちは");
Console.WriteLine("{0}さん、{1}日ぶりです。", name, num);
Console.WriteLine("5-3="+(5-3)+"です。");
Console.WriteLine("点数は"+num+"です。");
◎ 乱数
// 乱数の初期設定
Random rnd = new Random();
// 1以上7未満の乱数を発生させる(1から6まで)
int dice = rnd.Next(1, 7);
◎ 文字列の操作
比較
-
Compare()
戻り値 説明 負の整数 strAはstrBの前 0 strAはstrBと同じ順番 正の整数 strAはstrBの後ろ -
Equals()
戻り値 : bool値
string strA = "java";
string strB = "saba";
Console.WriteLine(string.Compare(strA, strB));
if(strA.Equals(strB)) {...}
文字列が数値を表しているかどうかを確認
if (int.TryParse(input_str,out int input_num)){...}
◆ array
// 配列の宣言
int[] arrayA;
int arrayB[];
// サイズを指定(配列インスタンスを生成)
arrayB = new int[3];
// 宣言と同時にサイズを指定
int[] arrayC = new int[7];
int[,] arrayCC = new int[5,7];
int[][] arrayGG = new int[4][]; // 不揃いの配列(ジャグ配列)
// 宣言と同時に初期化(newは省略可能)
int[] arrayD = new int[] {100, 200, 300};
int[] arrayE = {1,1,2,3,5,8,13};
// jagged
for(int m = 0; m < arrayGG.Length; m++){
for(int n = 0; n < arrayGG[m].Length; n++){
Console.Write(arrayGG[m][n]+" ");
}
}
// foreach loop
foreach(int i in arrayE){
Console.Write("{0} ",i);
}
四角配列の場合arrayCC[m].Length
を取得できない
◆ class
フィールド、メソッド
class Program {
static void Main(string[] args) {
Circle c = new Circle();
c.r = 4.0;
Console.WriteLine("半径" + c.r + "の円の円周の長さは" + c.Circumference());
Console.WriteLine("半径" + c.r + "の円の面積は" + c.Area());
}
}
/**
* 半径rの円に関する計算をするクラス。
*/
class Circle {
/**
* 半径を表すフィールド。
*/
public double r;
/**
* 円周の長さを求めるメソッド。
*/
public double Circumference(){
return 2*3.14*r;
}
/**
* 円の面積を求めるメソッド。
*/
public double Area(){
return 3.14*r*r;
}
}
オーバーロード:メソッド名同じで引数違うやつ
◎ 構造体
構造体とは、ある対象に関連する項目をまとめて1つのかたまりにしたもの。
できることはclassの完全下位互換。
classとはメモリの使い方が異なり、小さなデータ構造を扱いたいときは便利。
実際どのような場面で使うかというと、プリミティブ型を2つ3つ持つだけ、みたいな「軽量なオブジェクトで、かつ頻繁にnew()されるもの」に対して効果を発揮します。
インスタンス化ですべてのメンバが初期化されるので、引数なしコンストラクタは不必要。
namespace other2_struct_ {
struct Point {
public int X { get; }
public int Y { get; }
// コンストラクタ
public Point(int x, int y) { X = x; Y = y; }
public override string ToString() => $"({X}, {Y})";
}
struct Grid {
// フィールド
public int x;
public int y;
public int z;
// コンストラクタ
public Grid(int a,int b,int c){
x = a; y = b; z = c;
}
public int Add(){
return x + y + z;
}
public string Show() => $"({x},{y},{z})";
public double average() => Add() / 3;
}
class Program {
static void Main(string[] args){
var p1 = new Point(); // 既定値、つまり、「XもYも0に初期化」という意味で使われる
var p2 = new Point(10, 20);
Console.WriteLine(p1);
Console.WriteLine(p2);
Console.WriteLine("\n-=-=-=-=-=-=-=-=-\n");
Grid g = new Grid(54,67,18);
Console.WriteLine(g.Show());
Console.WriteLine(g.Add());
Console.WriteLine(g.average());
}
}
}
◎ getter, setter
private string myName; // メンバ変数
// プロパティ
public string MyName {
get { return myName; } // getterの部分
set { myName = value; } // setterの部分
}
public double Area {
get { return width * height; }
}
自動実装プロパティ
// 初期値が返される
public int Age { set; get; }
// ここで初期値を定義できる
public int Age { set; get; } = 25;
// 読み取り専用(setはprivate)
public string Name{
private set; get;
}
スニペット
// propと入力してTabキーを2回押す
public int MyProperty { get; set; }
// propf(propfullの略)と入力してTabキーを2回押す
private int myVar;
public int MyProperty {
get { return myVar; }
set { myVar = value; }
}
◆ constructor
クラスにコンストラクタを記述しなかった場合、コンパイラがデフォルトコンストラクタを自動生成する。
The default constructor initializes:
- All numeric fields in the class to zero.
- All string and object fields to null.
class Person{
//fields
private string name = "";
private int age = 0;
//constructor
public Person() : this("no name",0){
Console.WriteLine("引数なしコンストラクタ");
}
public Person(string name, int age){
this.name = name;
this.age = age;
Console.WriteLine("引数ありコンストラクタ");
}
}
◎ ガーベジコレクション
String[] = new String[10000];
for(int i=0;i<1000;i++){
a[i] = new String('M',10000); // 文字Mを10000個連ねた文字列を代入
}
console.WriteLine("メモリ使用量(GC発動前):" + GC.GetTotalMemory(false));
a = null; // aの参照を解放
console.WriteLine("メモリ使用量(参照解除後):" + GC.GetTotalMemory(false));
GC.Collect(); // ガーベジコレクション
console.WriteLine("メモリ使用量(GC発動後):" + GC.GetTotalMemory(false));
◎ デストラクタ
コンストラクタの反対で、オブジェクトが破壊されるときに呼び出される特殊なメソッド
class Dummmy {
// constructor
public Dummy(){
Console.WriteLine("コンストラクタ");
}
// destructor
~Dummy(){
Console.WriteLine("デストラクタ");
}
}
※ あいまいな名称
変数がフィールド(メンバ変数)
メソッド(関数)がメンバ
アクセサメソッド(getter,setter)がプロパティ(アクセサー)
と呼ぶ人が多いのか、言語によって違うしわからん
人によって定義が諸説あるのかなんでもいいじゃん
◆ static
static(静的クラス・静的メンバ)は、インスタンスに属さず、クラスそのものに属するメンバ(フィールドやメソッド)を作成するための機能
インスタンスを生成しなくても利用できる
静的メソッドを呼び出すときは、[クラス名].[メソッド名]([引数])
同様に、静的フィールドを呼び出すときは、[クラス名].[フィールド名]
// 静的メンバ
static int Add(int a, int b){ return a + b; }
static int Sub(int a, int b){ return a - b; }
static int Mul(int a, int b){ return a * b; }
static int Mul(int a, int b, int c){ return a * b * c; }
// main
static void Main(string[] args) {
// 乱数を発生させる
Random rnd = new Random();
int a = rnd.Next(1,11);
int b = rnd.Next(1,11);
int c = rnd.Next(1,11);
Console.WriteLine("a = {0}", a);
Console.WriteLine("b = {0}", b);
Console.WriteLine("c = {0}", c);
// Console.WriteLine("a * b = {0}", Mul(a, b));
Console.WriteLine("a * b * c = " + Mul(a, b, c));
}
class Counter {
// カウントするフィールド。
private int count = 0;
// トータルのカウント数を表す静的フィールド
private static int totalCount = 0;
// カウントのインクリメントメソッド
public void Increment() {
totalCount++;
count++;
}
// カウントをリセットするメソッド
public void Reset() {
totalCount -= count;
count = 0;
}
// 回数のプロパティ(getter)
public int Count {
get { return count; }
}
// トータルのカウントのgetter
public static int TotalCount {
get { return totalCount; }
}
}
◆ 継承
not extends but inheritance
// Calculatorクラスを継承した、ExCalculatorクラス
class ExCalculator : Calculator {
// 掛け算
public void mul(){ Console.WriteLine("{0} * {1} = {2}", num1, num2, num1 * num2); }
// 割り算
public void div(){ Console.WriteLine("{0} / {1} = {2}", num1, num2, num1 / num2); }
}
Calculator
: 親クラス、スーパークラス、ベースクラス
ExCalculator
: 子クラス、サブクラス
◎ コンストラクタ・デストラクタ
子クラスをnewすると、親クラスのコンストラクタが呼び出され、その後、子クラスのコンストラクタが呼び出されます。
◎ JAVAとの記述法の違い
class Triangle : PlaneFigure {
public Triangle(double w, double h) : base(w, h) { }
}
class Box extends PlaneFigure {
public Box(double w, double h) {
super(w,h);
}
}
◎ オーバーライド
class Child : Parent {
public void override Foo() {
Console.WriteLine("子クラスのFoo()メソッド");
}
}
- オーバーライドとオーバーロードを総称してポリモーフィズムと言うよ
- 子クラスは親クラスができることを全てできる。追記のかたち
◆ 抽象クラス
- 抽象メソッドを一つでも定義したクラス。
- それ自体はインスタンスすることができず、親クラスとなりうるクラス。
- 抽象メソッドとは、処理ブロックを記述しないメソッド。
- 子クラスで
override
して処理を記述する。 - すべての
abstract
をoverride
しないとコンパイルエラーになる。 - 継承して拡張するので単一継承のみ
public abstract class Sample{
protected String name = "";
public abstract String call();
}
子クラスごとに違う処理をしたいメソッドだけ抽象化する
◆ インターフェース
- それ自体はインスタンスすることができず、親クラスとなりうるクラス。
- 実装がないメソッド(抽象メソッド)のみで構成される
- 抽象メソッドと違い、多重実装できる
- 小クラスで実装が前提なので
public
protected
abstract
override
を記述する必要がない - インターフェースは、クラスの一部を切り取るためのものであり、メソッドの重複があってもかまわない
// 電子メール
interface IEmail {
// メールを送る
void SendMail(string address);
}
// 携帯電話クラス(IPhone、IEmailクラスを実装
class CellPhone : IEmail {
// メールアドレス
private string mailAddress;
// コンストラクタ(メールアドレスと電話番号を設定
public CellPhone(string mailAddress){
this.mailAddress = mailAddress;
}
// 指定したメールアドレスにメールを送信する
public void SendMail(string address){
Console.WriteLine(address + "に、" + this.mailAddress + "からメールを出します。");
}
}
class Program {
static void Main(string[] args) {
CellPhone cp = new CellPhone("hoge@email.com");
// 携帯電話クラスで、電話とメールを送る
cp.SendMail("fuga@email.com");
// メールインターフェースでインスタンスにアクセス。
IEmail mail = (IEmail)cp;
mail.SendMail("bar@email.com"); // メールを出す
}
}
インスタンスのキャスト
IEmail mail = (IEmail)cp;
CellPhoneクラスを、IEmail型にキャスト。
実装されているインターフェースのメンバしか使用できなくなる
余計なメンバへのアクセスの制限をかけることができる
◆ コレクション
配列の限界
配列は宣言時に要素数も宣言しなければならない。
あらかじめ格納するデータの数がわからない場合、Collectionが便利。
コレクション | 特徴 |
---|---|
配列(Array) | 固定長。インデックスで要素にアクセスできる |
リスト(List) | 可変長。インデックスで要素にアクセスできる |
ディクショナリ(Dictionary) 別名)連想配列,ハッシュテーブル |
可変長。キーと値を紐づけてアクセスできる |
スタック(Stack) | 可変長。先入れ後出し(FILO)式 |
キュー(Queue) | 可変長。先入れ先出し(FIFO)式 |
List
一番使用頻度の高い、Listクラスの使用例
|
|
|
|
その他主要メソッド
メソッド | 働き |
---|---|
Reverse() | 全体の要素の順序を反転させる。 |
Find() | 指定されたデータを探し、最も小さいインデックスを返す。 |
Exists() | 指定されたデータと同じものが存在するかどうかを調べる。 |
Clear() | 全てのデータを削除。 |
Sort() | オブジェクトを並べ替え。 |
Dictionary
// 連想記憶クラスの生成
Dictionary<String, String> capital = new Dictionary<String, String>();
// データの追加
capital["日本"] = "東京";
capital["イギリス"] = "ロンドン";
capital["フランス"] = "パリ";
capital["中国"] = "北京";
Console.WriteLine("世界の首都");
foreach (String s in capital.Keys){
Console.WriteLine("{0}の首都は{1}です。",s,capital[s]);
}
その他主要メソッド
メソッド | 働き |
---|---|
ContainsKey() | キーの存在を確認する。 |
ContainsValue() | 値の存在を確認する。 |
HashSet
データを重複なく管理するCollection
同じ要素がすでに存在している場合、要素をセットできない
「set」は「集合」の意味
|
|
◆ delegate
C#言語の最大の特徴の一つともいえる処理であるデリゲート(delegate)。デリゲートは、JavaやC++には、該当する概念がない独自の処理で、C#言語(というよりも、.NETフレームワーク)独自の処理。
デリゲートは、日本語で、「移譲する」という意味で、他のクラスのメソッドを参照するオブジェクトのことを指し、主にイベント処理などに用いられる重要な概念。
引数と戻り値の型が同じメソッドの役割を引き受けるオブジェクトを生成することができ、名前は違ってもあたかもそのメソッドを呼び出しているのと同じ結果を得ることが可能。
// デリゲート
delegate void Operation(int a,int b);
// Calcクラス
class Calc {
public void Sub(int a, int b) {
Console.WriteLine("{0} - {1} = {2}", a, b, a - b);
}
}
// Programクラス
class Program {
static void Add(int a, int b) {
Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
}
static void Main(string[] args) {
Calc c = new Calc();
// デリゲートの設定
Operation o1 = new Operation(Add);
Operation o2 = new Operation(c.Sub);
// デリゲートで設定したメソッドの呼び出し
o1(2, 1);
o2(2, 1);
}
}
-
delegate
の宣言
delegate void Operation(int a,int b);
指定した名前のデリゲートを生成するというもので、ここで指定した同一の戻り値の型、そして引数をとるメソッドを、オブジェクトとして利用できる。 -
処理の移譲と利用
まずはそのためのオブジェクトを生成する。
Operation o1 = new Operation(Add);
引数として、同一の戻り値の型、および引数を持つメソッドの名前を引数として与えると、生成されたインスタンスは、引数のメソッドとしてふるまう。
複数のメソッドを同時に呼び出す
デリゲートには、同型であれば複数のメソッドを追加できる。実行順序は追加された順。
// デリゲートの宣言
delegate void Action(int a);
// 1つ目の処理
static void Func1(int a) {
Console.WriteLine("a={0}",a);
}
// 2つ目の処理
static void Func2(int a) {
Console.WriteLine("a*2={0}", a * 2);
}
// 3つ目の処理
static void Func3(int a) {
Console.WriteLine("a*3={0}", a * 3);
}
static void Main(string[] args) {
// デリゲートaの作成
Action a = new Action(Func1);
// 処理の追加
a += new Action(Func2);
a += Func3;
// 処理の実行
a(3);
}
例外処理
例外とは
プログラムを実行中に発生する動作における予期せぬ事象(エラー)のことを、例外と言います。たとえば、0で割り算を行ったり、配列変数の範囲からはみ出してしまったりするようなものが、代表的な例外です。通常、例外が発生すると、その段階でプログラムは異常終了しますが、例外が発生した時点でそれを検知し、何らかの処理を行うことを、例外処理と言います。それによりシステムが強制終了することを回避できます。
構文
try{
(処理①)
}catch((例外クラス) 変数){
(処理②)
}finally{
(処理③)
}
サンプル
static void Main(string[] args) {
try{
for(int i = 0; i<= 5; i++){
int a = getNum(i);
int b = 5;
Console.Write(a + " / " + b + " = ");
Console.WriteLine(calc(a,b));
}
}catch(DivideByZeroException e){
Console.WriteLine("0による割り算発生");
}catch(IndexOutOfRangeException e){
Console.WriteLine("配列の範囲外にアクセスしました");
}finally{
Console.WriteLine("終了");
}
}
// 計算処理(例外を発生させる)
private static int calc(int a,int b){
return a / b;
}
// 範囲外に出たときの処理
public static int getNum(int index){
int[] num = { 1, 2, 3, 4 };
return num[index];
}
意図的に例外を発生させる
static int GetNum(int i){
int[] nums = { 300, 600, 900 };
if (i > nums.Length){
// 例外を発生させる
throw new IndexOutOfRangeException();
}
return nums[i];
}
static void Main(){
try{
int result = GetNum(4);
}
catch (IndexOutOfRangeException e){
Console.WriteLine("配列の範囲外にアクセスしました。");
}
}
演算子のオーバーロード
組み込み型(int
や string
など)とユーザー定義型(クラスや構造体)の関係と同じように、
演算子(+
や <
など)もユーザー定義のオーバーロードができる。
利用シーンはほぼない
実例
- StringBuilder
- 三項演算子
ジェネリクス
さまざまな型に対応するために、型をパラメータとして与えて、その型に対応したクラスや関数を生成する機能。
ポイント
- ジェネリック: 型だけ違って処理の内容が同じようなものを作るときに使う。
- ジェネリッククラス:IComparable { int CompareTo(T x, T y); }
- ジェネリックメソッド:T max(T x, T y) { ... }
ジェネリックメソッド
2つの値の大きいほうをとる関数(静的メソッド) Max
を作りたい
int Max(int x, int y){
return x > y ? x : y;
}
同じことをdouble型で行おうとすると、同じような関数をもう一つ追加する必要がある。
double Max(double x, double y){
return x > y ? x : y;
}
この問題に対してジェネリクスを用いれば、必要に応じていろいろな型に対応した Max
関数を生成できる。
public static Type Max<Type>(Type a, Type b)
where Type : IComparable { // 型引数中の型が満たすべき条件
return a.CompareTo(b) > 0 ? a : b;
}
int n1 = Max<int>(5, 10); // int 版の Max を明示的に呼び出し
int n2 = Max(5, 10); // int 版の Max が自動的に生成される
double x = Max(5.0, 10.0); // double 版の Max が自動的に生成される
string s = Max("abc", "cat"); // string 版の Max (辞書式順序で比較)
ジェネリッククラス
// int 専用版スタッククラス
class StackInt {
int[] buf;
int top;
public StackInt(int max) { this.buf = new int[max]; this.top = 0;}
public void Push(int val) { this.buf[this.top++] = val; }
public int Pop(){ return this.buf[--this.top]; }
public int Size{ get{return this.top; } }
public int MaxSize{ get{ return this.buf.Length; } }
}
// generics 版スタッククラス
class Stack<Type> {
Type[] buf;
int top;
public Stack(int max) { this.buf = new Type[max]; this.top = 0;}
public void Push(Type val) { this.buf[this.top++] = val; }
public Type Pop(){ return this.buf[--this.top]; }
public int Size{ get{return this.top; } }
public int MaxSize{ get{ return this.buf.Length; } }
}
// 参照例
const int SIZE = 5;
Stack<int> si = new Stack<int>(SIZE); // int型を格納できるスタックになる
Stack<double> sd = new Stack<double>(SIZE); // double型を格納できるスタックになる
for(int i=1; i<=SIZE; ++i){
si.Push(i);
sd.Push(1.0/i);
}
while(si.Size != 0){
Console.Write("1/{0} = {1}\n", si.Pop(), sd.Pop());
}
「インターフェース」、 「デリゲート」もジェネリックなものが定義できる。
制約条件
where
以下に、型引数が満たすべき条件(constraint: 制約条件)を書くことができる。
制約は付けなかった場合、型引数で与えた型に対するメソッド呼び出しなどは出来なくなる。
// 一番目の引数だけを帰す単純なメソッド。
static Type First<Type>(Type a, Type b){
// 特にメソッド呼び出し等はないのでこれは OK。
return a;
}
// Max 関数の where の部分を消してみる。
static Type Max<Type>(Type a, Type b){
// ↓Type 型に CompareTo なんて定義されていないと怒られてエラーになる。
return a.CompareTo(b) > 0 ? a : b;
}
where Type : IComparable
が必要。
IComparable
インターフェースが CompareTo
メソッドを持っているので、「 Type
は IComparable
を実装している」という制約を課すことで、メソッド中で a.CompareTo(b)
を呼び出せる。
指定できる制約
制約の与え方 | 説明 |
---|---|
where T : struct | 型Tは「値型」である |
where T : class | 型Tは「参照型」である |
where T : [base class] | 型Tは[base class]で指定された型を継承する |
where T : [interface] | 型Tは[interface]で指定されたインターフェースを実装する |
where T : new() | 引数なしのコンストラクタを持つ。他の制約条件と同時に課す場合には、一番最後に指定する必要がある |
where T : unmanaged | 型Tは「アンマネージ型」である |
where T : Enum | 型Tは「列挙型」である |
where T : Delegate | 型Tは「デリゲート型」である |
where T : notnull | 型Tには非 null な型しか渡せない |
既定値
default(Type)
を使うと、数値型に対しては 0
参照型に対しては null
になる。
共変性・反変性
List<string> strings = {"aa", "bb", "cc"};
List<object> objs = strings;
これを認めてしまうと、以下のような不正な値の書き換えが起こり得る。
// strings と objs は同じオブジェクト
objs[0] = 5; // int に書き換えられたらまずい
string str = strings[0];
この問題が起きる原因は、 List
が set
も get
も可能なインデクサーを持っていること。
常に安全な代入をしたい。
戻り値(get
)でしか使わない型を共変性(covariance)、
引数(set
)でしか使わない型を反変性(contravariance)という。
// covariance
IEnumerable<string> strings = new[] {"aa", "bb", "cc"};
// foreach (object x in strings) とやっても問題ないから、 objs に strings を代入しても OK。
IEnumerable<object> objs = strings;
// contravariance
Action<object> objAction = x => { Console.Write(x); };
// objAction("string"); とやっても問題ないから、 strAction に objAction を代入しても OK。
Action<string> strAction = objAction;
in/out修飾子(変性注釈)
共変性のある関数の作成
出力(メソッドの戻り値、プロパティの get)にしか使わない型には out
という修飾子を指定する。
public interface IEnumerator<out T>{
T Current { get; } // get しかない = 出力のみ
bool MoveNext();
void Reset();
}
こうすることで、共変性が認められる。
IEnumerator<string> strEnum = new Enumerator<string>();
IEnumerator<object> objEnum = strEnum;
反変性のある関数の作成
入力(メソッドの引数、プロパティの set)にしか使わない型には in
という修飾子を指定する。
public interface IComparer<in T>{
int Compare(T a, T b); // T は引数としてしか使われない
}
こうすることで、反変性が認められる。
IComparer<object> objComp = new Comparer<object>();
IComparer<string> strComp = objComp;
共変性と反変性のある関数の作成
in
/out
の組み合わせもあり得る。
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
Func<object, object, string> f1 = (x, y) => string.Format("({0}, {1})", x, y);
Func<string, string, object> f2 = f1;
LINQ
Language Integrated Query
リレーショナルデータベースや XML に対する操作をプログラミング言語に統合する機能
C# 等の言語に SQL ライクなデータベース操作構文を組み込む (+ データベースや XML 操作用のライブラリ)機能
LINQ を用いることで、様々なタイプのデータソースに対する検索や操作を、共通の構文で行うことができる。
var 学生名簿 = new[] {
new {学生番号 = 14, 姓 = "風浦", 名 = "可符香"},
new {学生番号 = 20, 姓 = "小森", 名 = "霧" },
new {学生番号 = 22, 姓 = "常月", 名 = "まとい"},
new {学生番号 = 19, 姓 = "小節", 名 = "あびる"},
new {学生番号 = 18, 姓 = "木村", 名 = "カエレ"},
new {学生番号 = 16, 姓 = "音無", 名 = "芽留" },
new {学生番号 = 17, 姓 = "木津", 名 = "千里" },
new {学生番号 = 8, 姓 = "関内", 名 = "マリア"},
new {学生番号 = 28, 姓 = "日塔", 名 = "奈美" },
};
// 学生番号が15以下の名を取得
var 出席番号前半名 =
from p in 学生名簿
where p.学生番号 <= 15
orderby p.学生番号
select p.名;
// output
foreach (var 名 in 出席番号前半名){
Console.Write("{0}\n", 名);
}