- 投稿日:2020-04-01T23:49:26+09:00
メンバーとかプロパティとかフィールドがわからなくなったのでまとめてみた
はじめに
雰囲気で理解していたら業務中に痛い目にあったので、真面目に調べてみることにしました。
調べていると、どうやらオブジェクト指向も理解しておく必要があると気づいたので、それらも含めてまとめてみました。メンバーとフィールドについて
こちらの方は比較的簡単のため、先に解説しておきます。
public class Person { const int ADULT = 20; // 定数(フィールドではない) private string Name; // フィールド private int Age; // フィールド // コンストラクター(フィールドではない) public Person(string name, int age) { this.Name = name; this.Age = age; } // メソッド(フィールドではない) public void printPerson() { if(this.Age > ADULT) { Console.Write(Name + "は成人しています"); } else { Console.Write(Name + "は未成年です"); } } }フィールド
クラスの中で定義してある変数部分のことです。
上のコードではNameとAgeです。
また、フィールドはメンバー変数とも呼ばれるようです。メンバー
メンバーは、上記のコードのコメントに書いてあるもの全てをさします。
フィールド, 定数, メソッド, コンストラクター全てがメンバーになります。また、フィールドや定数はデータメンバー、メソッドやコンストラクターを関数メンバーといいます。
プロパティについて
プロパティについて説明する前に、まずはオブジェクト思考の実装の隠蔽とカプセル化について説明しないといけません。
実装の隠蔽とカプセル化
実装の隠蔽とは
通常、内部の実装がどうなっているのかを隠蔽(要するに private にする)し、可能な操作のみを公開(public)することが望ましいとされています。 簡単に言うと、メンバー変数はクラス外部から直接アクセス出来ないようにして、オブジェクトの状態の変更はすべてメソッドを通して行うべきだということです。
実装の隠蔽 - C# によるプログラミング入門 | ++C++; // 未確認飛行 Cから引用文章だけでもかなりわかりやすく説明されていますが、コードを使ってもう少しわかりやすく説明します。
上で作ったPersonクラスに性別とニックネームの設定をしたいと思います。
NameとAgeはコンストラクターで設定しますが、性別とニックネームはコンストラクターで設定はしないとします。public class Person { public string Sex; // 性別 public string Nickname; // ニックネーム } public class Sample { person1 Taro = new person("Taro", 15); // 性別とニックネームを設定する Taro.Sex = "男"; Taro.Nickname ="tataroro"; // ニックネームを表示する Console.Write(Taro.Nickname); }まずは隠蔽しない場合の例です。
SexとNickNameのアクセスレベルはPublicであるので、他のクラスから直接指定することが出来ます。
これの何が問題かというとTaro.Sex = "ああああ"という設定も可能であるということです。ニックネームが「ああああ」であろうと問題ありませんが、性別は「男」「女」のみに設定したいと思います。次は隠蔽を行ったコードを書いてみます。
public class Person { private string Sex; // 性別 private string Nickname; // ニックネーム public void SetSex(bool judge) { this.Sex = judge ? "男" : "女"; } public void GetNickName() { return this.Nickname; } // フィールドのニックネームを返す public void SetNickName(string nickname) { this.Nickname = nickname; } // フィールドにニックネームを設定する } public class Sample { person1 Taro = new person("Taro", 15); // 性別とニックネームを設定する Taro.SetSex(true); Taro.SetNickName("tataroro"); // ニックネームを表示する Console.Write(GetNickName()); }SexとNickNameのアクセスレベルをprivateに変更したため、値を直接セットすることは出来なくなりました。
SexとNickNameに値を代入するには必ずメソッドを経由して値がセットされるため、Sexには男か女しか入りません。
(本来は男女を真偽値で設定するのはあまりよくありません。ここでは簡単な例として使用しています)このように変数を保護(private)して、外部からのアクセスにはメソッドを提供することをカプセル化と言います。
プロパティ
上記の内容を踏まえた上でプロパティの説明に入ります。
プロパティとは、クラス外部から見るとメンバー変数のように振る舞い、クラス内部から見るとメソッドのように振舞うものです。いまいちイメージしづらいので、実際にコードを使って説明します。
public class Person { private string Sex; // 性別 private string _nickname; // ニックネーム public void SetSex(bool judge) { this.Sex = judge ? "男" : "女"; } // プロパティ public string NickName { get { return _nickname; } // フィールドのニックネームを返す set { _nickname = value; } // フィールドにニックネームを設定する } } public class Sample { person1 Taro = new person("Taro", 15); // 性別とニックネームを設定する Taro.SetSex(true); Taro.SetNickName = "tataroro"; // setの部分が呼び出される // ニックネームを表示する Console.Write(Taro.SetNickName); // getの部分が呼び出される }このコードの処理は一つ前のコードと同じ処理をしています。
一つ前のコードでは、SetNickName、GetNickNameのメソッドを用いて値の設定と表示を行っていましたが、今回はプロパティNickNameに変更して同じような処理を行っています。(プロパティ名とフィールド名を区別するために、フィールド名は_nicknameに変更しています)プロパティを用いることによりPersonクラスではメソッドの役割をするNickNameが、Sampleクラスからはメンバ変数と同じ扱い方をしていることがわかりますね。
これが上記で説明した「クラス外部から見るとメンバー変数のように振る舞い、クラス内部から見るとメソッドのように振舞うものです。」ということです。また上のプロパティの記述は{ get; set; }と短縮することも可能です。
public string SetNickName { get { return NickName; } set { Sex = value; } } // 上のプロパティは以下のように短縮できる public string SetNickName { get; set; }おわりに
点と点の知識が上手く繋がった気がしました。勉強する前は、カプセル化をの概念が曖昧だったため、プロパティのメリットなどを理解していませんでした。
間違った部分などがあればコメントにてお願いします。参考
【C#】「カプセル化」をもう一度学ぶ
プロパティ?フィールド?メンバー?C#のクラス構造のおさらい
C#でカプセル化を使ったオブジェクト指向設計の実装方法を解説!!
カプセル化するとインスタンス変数は賢くなる
C# の型とメンバー - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
プロパティ - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
実装の隠蔽 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
- 投稿日:2020-04-01T18:10:53+09:00
C#で関数ポインタを作った話。
謝辞
この記事は
- https://www.infoq.com/jp/news/2019/03/CSharp-Static-Delegate/
- https://qiita.com/pCYSl5EDgo/items/4a5cc446553f4d99a7f3
- https://ufcpp.net/study/csharp/functional/miscdelegateinternal/
- https://github.com/DotNetCross/Memory.Unsafe/tree/master/src/DotNetCross.Memory.Unsafe
- https://www.asukaze.net/etc/cil/calli.html
以上の五つを非常に参考にさせていただいています。
特に2の記事はほとんどパクらせていただいております。
UnsafeクラスはILの書き方のサンプルとして使わせていただきました。初めに
C#にはデリゲートが存在します。
ですがデリゲートはスタティックメソッドを呼び出すには適していません。(詳細)
どうにかならないかと考えているとこんな記事があり、どうやら関数ポインタが追加されるかもしれないということを知りました。
ですが、待ちきれないので作ることにしました。
関数ポインタを取得する。
先人はリフレクションで取得されていましたが、デリゲートをいじっているとまんまなものが。
Delegate.Method.MethodHandle.GetFunctionPointer()
これじゃん。
ということで関数ポインタはこの関数で取得します。
実装
//Func等をキャストなしで引数に渡せるのでジェネリックにしています。 IntPtr GetFunctionPointer<T>(T function) where T : Delegate { return function.Method.MethodHandle.GetFunctionPointer(); }
関数を呼び出す。
上の関数の戻り値はIntPtrです。
このままでは関数を呼ぶことはできません。
かといってリフレクションを使うと結局デリゲートになってしまいます。
こんな時は、、、そう IL です。ILには複数の関数呼び出し命令が存在しますが、今回はIntPtrを用いるのでcalliという命令を使います。
コンパイルはilasm.exe(Windowsに付属のILコンパイラ)を利用しました。使い方
ilasmは通常以下のディレクトリに存在します。
C:\Windows\Microsoft.NET\(任意のフレームワークフォルダ)\(任意のバージョンフォルダ)
実装
//戻り値なしの関数(項数1, 2)のみ例示します。 //実際はFunc等に合わせて項数8までのオーバーロードがあるといいと思います。(実際書いた) .method public hidebysig static void Call(native int function) cil managed aggressiveinlining { .maxstack 1 ldarg.0 calli void() ret } .method public hidebysig static void Call<T0>(native int function, !!T0 arg0) cil managed aggressiveinlining { .maxstack 2 ldarg.1 ldarg.0 calli void(!!T0) ret }
終わり?
以上でめでたく関数を呼び出せました。
ですがこれ、とても危険だと思いませんか?端から関数ポインタの取得をしている時点で危険ですけれども。
とにかく、このままだといかなる関数でも同じ処理ができてしまいます。
もしFunc<int, object>をCall<int, int>に渡してしまったらどうなるでしょう。
それでも実行時まで何もわかりません。これでは静的型付け言語たるC#のアイデンティティを失ってしまうので、
Func、Actionを見習ってラッパーを作りましょう。ラッパーを作る。
安全に扱える関数の条件はなんでしょう。
まずデリゲートにインスタンスメソッドが代入されている場合を考えてみます。
インスタンスメソッドは暗黙的にthis参照を受け取っています。
ですがデリゲートの引数としては扱われません。
なのでメソッドの呼び出しがおかしくなってしまいます。
よってインスタンスメソッドは受け取れません。
(実はちょっと工夫すればインスタンスメソッドを受け取れますが、デリゲートと変わらなくなるのでやりません。)次にスタティックメソッドが代入されている場合を考えてみます。
スタティックメソッド通常は引数の並びがそのままです。
また参照の寿命等も考えなくてよいです。
よってスタティックメソッドは受け取って大丈夫ですね、、、通常ははい。再三強調したようにスタティックメソッドでも引数の並びが一致しない時があります。
それは拡張メソッドの時です。
拡張メソッドはまるでインスタンスメソッドのようにスタティックメソッドを扱える構文です。
デリゲートに代入するときも同様で、インスタンスメソッドのように代入できます。
ですが、それによりインスタンスメソッドが代入されているとき同様の問題を持ちます。以上より、安全に呼び出せる関数は拡張メソッドではないスタティックメソッドであることが分かりました。
というわけでFunc、Actionに似せたラッパーを作ります。
実装
//項数1の戻り値有り・無しで一つずつ例示します。 public readonly struct FuncPointer<TResult, T0> : IEquatable<FuncPointer<TResult, T0>> { public static bool operator ==(FuncPointer<TResult, T0> left, FuncPointer<TResult, T0> right) => left.Equals(right); public static bool operator !=(FuncPointer<TResult, T0> left, FuncPointer<TResult, T0> right) => !(left == right); public static int Arity => 1; public RuntimeMethodHandle Handle { get; } readonly IntPtr ptr; [MethodImpl(MethodImplOptions.AggressiveInlining)] public TResult Invoke(T0 arg0) => FunctionPointerUtility.Call<TResult, T0>(this.ptr, arg0); public override bool Equals(object obj) => obj is FuncPointer<TResult, T0> pointer && this.Equals(pointer); public bool Equals(FuncPointer<TResult, T0> other) => this.Handle.Equals(other.Handle); public override int GetHashCode() => HashCode.Combine(this.Handle); public FuncPointer(Func<TResult, T0> func) { FunctionPointersHelper.ValidateFunction(func, Arity); this.Handle = func.Method.MethodHandle; this.ptr = this.Handle.GetFunctionPointer(); } } public readonly struct ActionPointer<T0> : IEquatable<ActionPointer<T0>> { public static bool operator ==(ActionPointer<T0> left, ActionPointer<T0> right) => left.Equals(right); public static bool operator !=(ActionPointer<T0> left, ActionPointer<T0> right) => !(left == right); public static int Arity => 1; public RuntimeMethodHandle Handle { get; } readonly IntPtr ptr; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Invoke(T0 arg0) => FunctionPointerUtility.Call(this.ptr, arg0); public override bool Equals(object obj) => obj is ActionPointer<T0> pointer && this.Equals(pointer); public bool Equals(ActionPointer<T0> other) => this.Handle.Equals(other.Handle); public override int GetHashCode() => HashCode.Combine(this.Handle); public ActionPointer(Action<T0> action) { FunctionPointersHelper.ValidateFunction(action, Arity); this.Handle = action.Method.MethodHandle; this.ptr = this.Handle.GetFunctionPointer(); } } //ResourceStringsは特に重要でないので出しません。 internal static class FunctionPointersHelper { public static void ValidateFunction(Delegate func, int arity) { if (func is null) throw new ArgumentNullException(ResourceStrings.ExceptionMessage_FunctionIsNull); if (!func.Method.IsStatic) throw new ArgumentException(ResourceStrings.ExceptionMessage_FunctionIsNotStatic); //拡張メソッドだと項数が一致しない。 if (func.Method.GetParameters().Length != arity) throw new ArgumentException(ResourceStrings.ExceptionMessage_FunctionArityMismatch); } }
これで本当に完成ですね!
終わりに
こんな記事を作っておいてなんですが、正式に実装される時を待ちましょう!(本末転倒)
感想
- パフォーマンスの測定をした結果。 連続呼び出し回数が少ないときは関数ポインタが圧倒的に早いですが、 連続呼び出し回数が多くなってくるとあまり差がなくなる様です。
- ILを手書きした所感。 手書きはそこまで難しくないということですね。 動的に生成する方が圧倒的につらかったです。
n番煎じな記事でしたが、最後まで読んでいただきありがとうございました。
- 投稿日:2020-04-01T16:01:10+09:00
【WPF】ComboBox上でマウスホイールを回した時に項目が変わらないようにする
動機
ComboBoxの標準の挙動って、フォーカスがある状態でマウスカーソルを乗せてホイールを回すと選択項目が順番に変わるんですよね。
これはこれで便利なのですが、無効化したいこともあると思います。なので、その方法を。Stack Overflowより
Disable MouseWheel in editable ComboBox as ItemTemplate
この回答でほぼ解決なのですが、微妙に惜しいと思うので改良版を作ってみました。ちなみに、
e.Handled = true;
以下の部分は親要素にホイールのイベントを発生させています。
これがないとComboBoxの上にカーソルがある時に、ホイールで親要素がスクロールしなくなります。改良版
class ComboBoxHelper : Behavior<ComboBox> { public static readonly DependencyProperty IsWheelDisabledProperty = DependencyProperty.RegisterAttached("IsWheelDisabled", typeof(bool), typeof(ComboBoxBehavior), new PropertyMetadata(false, PropertyChanged)); public static bool GetIsWheelDisabled(DependencyObject obj) { return (bool)obj.GetValue(IsWheelDisabledProperty); } public static void SetIsWheelDisabled(DependencyObject obj, bool value) { obj.SetValue(IsWheelDisabledProperty, value); } private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var comboBox = (ComboBox)d; if ((bool)e.NewValue) { comboBox.PreviewMouseWheel += ComboBox_PreviewMouseWheel; } else if (!(bool)e.NewValue) { comboBox.PreviewMouseWheel -= ComboBox_PreviewMouseWheel; } } private static void ComboBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { var comboBox = (ComboBox)sender; if (!comboBox.IsDropDownOpen) { e.Handled = true; var parent = (FrameworkElement)comboBox.Parent; var args = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent, Source = sender, }; parent.RaiseEvent(args); } } }使い方
<ComboBox ComboBoxHelper.IsWheelDisabled="True" />お手軽にホイールを無効化できますね。依存関係プロパティにしているのでバインディングもできます。便利!
- 投稿日:2020-04-01T13:48:16+09:00
C# UnixTime (int)を取得する
現在のUnixTimeをintで取得する
public static int GetCurrentUnixTime() { var unixTimestamp = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; return unixTimestamp; }指定した時間のUnixTimeをintで取得する
public static int GetUnixTime(DateTime timeStamp) { var unixTimestamp = (int)(timeStamp.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; return unixTimestamp; }UnixTime から Datetimeを取得
public static DateTime GetDateTimeFromUnixTime(int unixTimeStamp) { DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime(); return dtDateTime; }
- 投稿日:2020-04-01T08:42:17+09:00
ソースと出力が同じC#プログラム
初めに
エイプリルフールだけどネタを準備していなかったので 10年前にやったネタの再掲。
ソースと出力が同じ C#プログラム
FixedPoint.csusing System; namespace FixedPoint { class Program { static void Main(string[] args) { string strX = @"using System; namespace FixedPoint { class Program { static void Main(string[] args) { string strX = @""[X]""; Console.Write(strX.Replace(""["" + ""X"" + ""]"", strX.Replace(@"""""""",@""""""""""""))); } } } "; Console.Write(strX.Replace("[" + "X" + "]", strX.Replace(@"""",@""""""))); } } }解説っぽくない解説
このプログラムは再帰定理(再帰関数は再帰を使わない形で記述できる)の証明を参考にして作ったと記憶している。
発表元