Edited at

C#の便利な機能についての備忘録

More than 1 year has passed since last update.

※以下の記述は不定期に追加・訂正されることが有ります


string関係


  • 内部の文字コードは基本的にUTF-8

  • 文字コードを変換する際は、一旦バイト列に変換してからエンコードし直す

string sjis_str = "シフトJISな文字列";

var src = System.Text.Encoding.GetEncoding("shift_jis");
var dest = System.Text.Encoding.UTF8;
byte[] temp = src.GetBytes(sjis_str);
byte[] utf8_temp = Encoding.Convert(src, dest, temp);
string utf8_str = dest.GetString(utf8_temp);


  • 「U+0301」など、Unicodeで特定の文字を数字指定する場合は'\u0301'などと書くようにする

  • string#Replaceメソッドだとstr.Replace("A", "B")と記述するが、System.Text.RegularExpressions.Regex#ReplaceメソッドだとRegex(str, "A|B", "C")と記述することに注意

  • DateTime#ToString("~")メソッドで日付・時刻を自在な書式で表示できる。また、一部の書式については専用のメソッドでより短く記述できる

DateTime dt = DateTime.Now;

// "2017/05/08 13:35:44"
dt.ToString();
// "2017/05/08"
dt.ToShortDateString();
// "2017年5月8日"
dt.ToLongDateString();
// "13:35"
dt.ToShortTimeString();
// "13:35:44"
dt.ToLongTimeString();
// "2017-05-08 13-35-44"
dt.ToString("yyyy-MM-dd HH-mm-ss");
// "2017年5月8日(月) 午後1時35分44秒"
dt.ToString("y年M月d日(ddd) tth時m分s秒");


Enumerable


  • foreachで反復したいが同時にインデックスを取りたい場合、Selectを使えばインデックスを付与できる

// 参考:

// .NET TIPS:foreachループで現在の繰り返し回数を使うには?[C#/VB] - @IT
// http://www.atmarkit.co.jp/ait/articles/1702/22/news019.html

using System.Collections.Generic;
using System.Linq;

List<int> list;
foreach(var item in list.Select((Value, Index) => new {Value, Index})){
// Indexは0オリジンなことに注意
System.Console.WriteLine($"{item.Index} - {item.Value}");
}


  • 特定の条件を満たした要素のインデックスを取得したい場合、上記の手法でインデックスを付与したものに対してWhereで絞り込みを掛ければいい



using System.Collections.Generic;

using System.Linq;

// ここではUserというクラスのNameプロパティの値が"hoge"と等しいもののインデックスを探している
List<User> list;
// Where内に条件式を書いた場合
int index = list.Select((Value, Index) => new {Value, Index})
.Where(v => v.Value.Name == "hoge")
.First().Index;
// First内に条件式を書いた場合
int index = list.Select((Value, Index) => new {Value, Index})
.First(v => v.Value.Name == "hoge").Index;
// Firstだと未発見時に例外を投げるので、未発見時に型の既定値が返るFirstOrDefaultの方がいいことも。
// 次のコードではnull 条件演算子とnull 合体演算子により、未発見時に-1が返るようになっている
// (提供:chitoku氏)
int index = list.Select((v, i) => new { Value = v, Index = i })
.FirstOrDefault(x => x.Value.Name == "hoge")?.Index ?? -1;


  • ToDictionaryメソッドは1引数のものと2引数のものがあることに注意

// リストを初期化。この際listは

// [["水素","H"], ["ヘリウム","He"], ["リチウム","Li"]]
// といった構造になっている
var list = new List<List<string>>{
new List<string>{"水素", "H"},
new List<string>{"ヘリウム", "He"},
new List<string>{"リチウム", "Li"}
};
// ToDictionary(1引数版)。この際dic1は
// {"水素" => ["水素","H"], "ヘリウム" => ["ヘリウム","He"], "リチウム" => ["リチウム","Li"]}
// といった構造になっている
var dic1 = list.ToDictionary(p => p[0]);
// ToDictionary(2引数版)。この際dic2は
// {"水素" => "H", "ヘリウム" => "He", "リチウム" => "Li"}
// といった構造になっている
var dic2 = list.ToDictionary(p => p[0], p => p[1]);


  • そもそもLINQで使える全メソッド一覧が知りたい人はこちらの記事がオススメです

 LINQの拡張メソッド一覧と、ほぼ全部のサンプルを作ってみました。 - 地平線に行く


async/await

 ざっくりとした説明↓

C8gKouJVwAAD77l.jpg

 ちゃんとした説明はこれらのページを参照するのがオススメ

  できる!C#で非同期処理(Taskとasync-await) - kekyoの丼

  Taskを極めろ!async/await完全攻略

追記(2017/04/12):

Task.Runはスレッドプールを使用するので、スレッドが新規作成されるとは限りません。訂正します。

 [雑記] スレッド プールとタスク - C# によるプログラミング入門 | ++C++; // 未確認飛行 C


XAML周り


Data Binding


  • ComboBoxで要素を動的に決定する場合は、ItemSourceをBindingすると便利

  • もちろんListBoxなどでも使える


sample1.xaml

<!-- DataListが差し込むリスト -->

<ComboBox ItemsSource="{Binding DataList}">
<ComboBox.ItemTemplate>
<DataTemplate>
<!-- DataTemplateの内部で、ComboBoxに表示する各項目を設定する。
差し替えればButtonだろうとCheckBoxだろうと簡単に仕込めるのがWPFの強み。
また、DataListの要素からプロパティやフィールドを参照したい場合は、
{Binding Name}などと記述すれば参照できる -->

<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>


sample1.cs

// ただのListとかでなくObservableCollectionにしたのは慣例。

// より正確に言えば、ObservableCollectionだと中身を弄った際に
// GUI(今回はComboBox)が自動的に更新されて便利
public ObservableCollection<string> DataList{ get; set; }


なんでTextBoxに「追記時自動スクロール」するプロパティ項目がないんだよ!WinFormsにはあるのにぃぃぃ!!

 というわけで自前実装するのですが、多分これが一番早いと思います

 Behavior機能を使う都合上、System.Windows.Interactivityへの参照が必要ですので気をつけましょう。


XAML側はInteraction.BehaviorsでBehaviorを貼り付けます.xml

<Window xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">

<TextBox>
<i:Interaction.Behaviors>
<local:TextBoxScrollBehavior />
</i:Interaction.Behaviors>
</TextBox>


C#側はBehaviorを作成・配置します.cs

using System;

using System.Windows.Controls;
using System.Windows.Interactivity;

namespace DrstOpt.ViewModels
{
class TextBoxScrollBehavior : Behavior<TextBox>
{
protected override void OnAttached() {
base.OnAttached();
AssociatedObject.TextChanged += TextBox_Changed;
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.TextChanged -= TextBox_Changed;
}
void TextBox_Changed(object sender, EventArgs e) {
AssociatedObject.ScrollToEnd();
}
}
}



その他

 別途記事に書いたのでそれを参照すること

  WPFにおけるGUI構築講座 -座標ベタ書きから脱却しよう-

  WPFアプリを高DPI対応にしよう!


MVVM


  • C#においてWPFは、MVVMパターンを記述しやすい環境である

  • だが、Visual Studioが作成したプロジェクトのファイル構成は、MVVM向きではない

  • ゆえに以下では簡単にMVVMパターンを構築するためのメモを示す


変更前のプロジェクト構成

image


変更後のプロジェクト構成

image


ソースコード上の処理


  • MainWindow.xamlを無くしてMainView.xamlに書き直す

  • ViewModel部分はMainViewModel.csに記述する。定形ロジックを毎回書きたくないので継承を利用するといい

/* 旧コード */

class MainViewModel : INotifyPropertyChanged
{
(中略)
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string parameter) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
}

/* 新コード */
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string parameter) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
}
class MainViewModel : ViewModelBase
{
(中略)
}


  • MainWindow.xaml.csにイベント処理を記述していた場合、MainViewModel.csでICommandを利用するように書き換える。ただしICommandを利用するようにすると記述量が増えるので、継承を利用するといい

/* 旧コード */

// .xaml
<Button Click="Button_Click"/>
// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e) {
(中略)
}

/* 新コード */
// .xaml
<Button Command="{Binding ButtonCommand}"/>
// CommandBase.cs
public class CommandBase : ICommand
{
// デリゲートを保持するためのフィールド
Action action;
// ICommandを継承したことで生じるプロパティ
public bool CanExecute(object parameter) => true;
public event EventHandler CanExecuteChanged;
// デリゲートを実行するメソッド
public void Execute(object parameter) { action(); }
// コンストラクタ
public CommandBase(Action action) { this.action = action; }
}
// MainViewModel.cs
class MainViewModel : ViewModelBase
{
public ICommand ButtonCommand{ get; private set; }
private void ButtonAction(){
(中略)
}
// コンストラクタ
public MainViewModel() {
ButtonCommand = new CommandBase(ButtonAction);
}
}


  • App.xamlのStartupUriの項目を削除し、App.xaml.csを書き直す

    (「SampleApplication」の部分はプロジェクト名)

// App.xaml

<Application x:Class="SampleApplication.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DownloadSample1">
<Application.Resources>
</Application.Resources>
</Application>
// App.xaml.cs
/// <summary>
/// App.xaml の相互作用ロジック
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
// メイン画面を作成して表示する
var mv = new MainView();
var mvm = new MainViewModel();
mv.DataContext = mvm;
mv.Show();
}
}


  • MVVMなので、各フォルダ内の担当は次のように書くべきである


    • View → XAMLにUIを記述

    • ViewModel → csにData Binding処理を書く

    • Model → csにロジック処理を書く



※参考資料:

 MVVM を意識した内部構造 - YKSoftware - for WPF Developers


多言語対応

 次のページに詳しく書きました!

  C#でアプリを多言語対応した際に行ったことまとめ


その他便利な機能


  • 時間計測を行う際は、StopWatchクラスを用いると良い

var sw = new System.Diagnostics.Stopwatch();

// 計測開始
sw.Start();

// この間に処理を行う

// 計測終了
sw.Stop();
// 結果を返す(ミリ秒単位)
long time = sw.ElapsedMilliseconds;


  • 乱数を用いる際は、Randomクラスが楽である


    • ただし乱数の質に問題があるので、別途メルセンヌ・ツイスタ等の真っ当なアルゴリズムを使用することが推奨される。System.Randomっぽく叩けるものとしては、例えば「C#乱数ライブラリ」がある。



// 乱数を初期化

var r = new System.Random();
// 0以上6未満の整数一様乱数を発生させる
int dice = r.Next(6);
// -10以上11未満の整数一様乱数を発生させる
// つまり下記は[-10,10]であり[-10,11]ではない
int x = r.Next(-10, 11);
//0以上Int32.MaxValue未満の整数一様乱数を発生させる
int y = r.Next();
//0.0以上1.0未満の実数一様乱数を発生させる
// つまり[0.0,1.0)であり[0.0,1.0]ではない
double z = r.NextDouble();


  • 一部の警告を消したい場合の作法



    • CSXXXXを消したい時は、消したい警告が出る箇所より前に#pragma warning disable XXXXと書きます


    • CSXXXXCSYYYYを消したい時は、#pragma warning disable XXXX,YYYYと書きます

    • 逆にCSXXXXを復活させたい時は、消したい警告が出る箇所より前に#pragma warning restore XXXXと書きます