SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

翔泳社 新刊紹介(AD)

歴戦のC#プログラマが教えるおすすめレシピとは? 最新の便利コードへアップデートする『C#逆引きレシピ』

  • このエントリーをはてなブックマークに追加

 翔泳社が6月9日に刊行した『C#逆引きレシピ』は、3割がC#の文法、1割がC#に関するツール、残りがC#で.NET Frameworkを使うためのレシピ集です。その目標は、Visual StudioやMSDNなどのサポートなしでもサバイバルできるようになること。そこで今回、著者のartonさんにおすすめのレシピを紹介していただきました。

  • このエントリーをはてなブックマークに追加
C#逆引きレシピ

Amazon   SEshop   その他

C#逆引きレシピ

著者:arton
発売日:2016年6月9日(木)
価格:3,132円(税込)

本書について

本書は開発の現場で活躍する著者が、C#を利用した開発時に役立つTIPSをまとめた書籍です。実際の現場で「さっ」とひけるよう、利用頻度の高いカテゴリ別に分け、実際の開発現場で使えるTIPSをふんだんに用意しています。

『C#逆引きレシピ』から開発が捗るおすすめレシピを

第04章 ステートメントと特殊な演算子

060 ネストしたループから抜け出したい

gotoステートメント

 gotoステートメントは指定したラベル(末尾を「:」で終わらせた識別子)へ制御を移します。有名なgoto有害論(NOTEを参照してください)のgotoとほぼ同等の機能です。ただし、飛び先に指定するラベルは、変数の可視性ルールにしたがいます。つまり、ブロックの内側やメソッドをまたがるラベルは指定できません。

NOTE:gotoステートメントの是非

 現代では、まるで「くだん(半人半牛の妖怪)」のように実際に使われているコードを見たこともないのに嫌い恐れる人が存在するのが興味深い点です。

 gotoを使わずに多重ループを抜けるために、状態変数を導入して複雑なif文と多数のbreakを書くのであれば1つのgotoを記述すべきです。

x, y, zの3つのループの内側からの脱出
 for (var x = 1; x < 100; x++)
 {
  for (var y = 1; y < 100; y++)
  {
   for (var z = 1; z < 100; z++)
   {
    // xの2乗が3y+5zと等しくなったら処理を終了する
    if (Math.Pow(x, 2) == 3 * y + 5 * z)
    {
     Console.WriteLine($"x^2 == 3 * y + 5 * z; x={x}, y={y},z={z}");
     goto found; // ラベル末尾の「:」は書かない
    }
   }
  }
 }
 Console.WriteLine("unknown"); // 最後までループを走ったら見つからなかったことになる
found: // ラベルは識別子+「:」で記述する
 Console.WriteLine("end");

「私自身はループが深くならないようにメソッドへ分割するからgotoを使う機会はほとんどないのですが、多重ループからの脱出にifとbreakを各層に置いた汚いコードは目にします。そういうコードを見ると、gotoを使うほうがいいのになぁと思いますね」

第08章 クラス

160 複数の値を返すメソッドを作成したい(Tupleを使いたい)

Tupleクラスを利用する

 Tupleクラスを利用すると、クラスや構造体を定義せずに型情報付きで複数の値を1つのオブジェクトとして扱えます。たとえばコレクションへの格納、メソッド引数、戻り値などです。Tupleは、複数の型と値の組み合わせを運ぶための簡易オブジェクトで、Tuple.Create静的メソッドを利用して作成します。

Tupleとは

 Tuple(タプル)とは、ペア(2つ組)、トリプレット(3つ組)などを一般化した「組」で、かつ構成要素の順序づけが決まっているものを言います。例えば、本レシピのサンプルではItem1とItem2 は異なる意味を持つため順序が重要であり、tupleを用いることができます。

複数の値を返すメソッドの戻りにTupleを利用する
using System;
class Calc
{
 // 2つのint型を格納するTupleを返す。Tupleには型パラメーター<>が必須
 static Tuple Div(int x, int y)
 {
  // Item1に商、Item2に剰余を保持するTupleを生成する
  return Tuple.Create(x / y, x % y);
 }
 static void Main()
 {
  var result = Div(81, 13);
  // 割り算の商と余りを表示する
  // Tupleが格納するオブジェクトへはItem1、Item2……プロパティでアクセスする
  Console.WriteLine($"81 / 13 = {result.Item1}...{result.Item2}");
 }
}
// 出力
// 81 / 13 = 6...3

「Tupleは便利ですが、意外と実装がきれいではなくなるんですね。ですが、便利なものほどすぐに変化していくのがC#のいいところなので、どんどんブラッシュアップされていくと思います。せっかく載せたので、すぐに古くなりそうなのは悲しいですけどね」

第11章 ファイルの制御

204 安全なファイルの更新方法を知りたい

安全なファイルの更新とは

 重要なファイルについては、プログラムのバグ、ディスクフルを含む実行時のディスク障害、処理対象のデータの異常などによる消失や破壊されたデータだけが残ることを避ける必要があります。

 安全にファイルを更新するには、以下の手順を取ります。

  1. 更新対象のファイルを読み取り専用で開く
  2. 更新後のファイルを書き込み用に開く
  3. 更新は(2)のファイルに対して行う(更新処理は、(1)から(2)への更新処理を伴うフィルタリング処理とする)
  4. (1)のファイルをバックアップ用に名前を変更する
  5. (3)で処理が完了したファイルを(1)のファイルに置き換える

 このようにすると、ディスクに必要となる容量は一時的に元のファイルの倍以上となりますが、どの時点で障害が発生しても、更新前のファイルを復元できます。

File.Replace静的メソッドを利用する

 FileクラスのReplace静的メソッドは同一ディスクドライブ上のファイルについて上記の(4)~(5)の手順を実行します。

// 更新用ファイルは一時ファイルとする
var info = new FileInfo(Path.GetTempFileName ( ));
try
{
 // 更新対象ファイルを読み込み専用でオープンする
 using (var reader = File.OpenText(args[0]))
 // 更新対象ファイルを書き込み用にオープンする
 using (var writer = File.CreateText(info.FullName))
 {
   var line = string.Empty;
   while ((line = reader.ReadLine ( )) != null)
   {
    // 更新処理を行う
    writer.WriteLine(line.Replace("vb", "cs"));
   }
  }
 // File.Replaceは
 // 第1引数で指定したファイルを
 // 第2引数で指定したファイルに置き換える
 // 第3引数で指定したファイルに元の第2引数で指定したファイルはバックアップされる
 File.Replace(info.FullName, args[0], Path.ChangeExtension(args[0],".backup"));
}
finally
{
 // 正常に終了した場合は、File.Replaceメソッドによって削除(移動)済みとなる
 info.Delete ( );
}

「File.Replaceの発想はいいんですが、異なるドライブのファイルを扱えない点で惜しいメソッドです。最初はFile.Replace静的メソッドを使わない方法で考えていましたが、長くなってしまうので使うことにしました。もちろん同じドライブしか使えないと記載しておきました。すべてを同じドライブで実行するのは、ディスク障害に対しては安全ではないのですが、それに目をつぶれば志が高いメソッドではあります(笑)」

第14章 LINQ

232 NameValueCollectionをLINQで使いたい

コレクションにLINQが適用できない場合はIEnumerable<T>を作る

 NameValueCollectionとその派生クラスは、旧型のコレクション(IEnumerable)なのでLINQを適用できません。

 このような場合以下の手順でIEnumerable<T>を作ります。

  1. Keys(またはAllKeys)プロパティに拡張メソッドのCast<T>をキーの型を指定して適用します。
  2. Castメソッドが返すIEnumerable<T>のSelectメソッドを呼び出します。Selectに与えるラムダ式で、引数に与えられたキーを元のNameValueCollectionに適用して値を取得し、キーと値のペアを格納する匿名クラスかタプルを返します。
  3. (2)によってキーと値のペアのIEnumerable<T>が得られるので、以降は通常通りにLINQで記述します。
WebResponseのHeadersプロパティ(WebHeaderCollection)にLINQでアクセスする
// using System.Net;が必要
// URIを与えてHttpWebRequestを生成する
var req = WebRequest.CreateHttp("http://example.com/");
req.UserAgent = "testagent";
req.Accept = "*/*";
// Webサーバーからレスポンスを得る
using (var resp = req.GetResponse())
{
 // WebResponse.HeadersはWebHeaderCollectionなのでそのままではLINQを適用できない
 // Cast<string>を呼び出してキーのIEnumerable<string>を得る
 foreach (var h in resp.Headers.Keys.Cast<string>()
  // Selectを呼び出してキーと値の匿名クラスのオブジェクトに変換する
  .Select(k => new { Key = k, Values = resp.Headers.GetValues(k) })
  // キーにContentが含まれるものを抽出する
  .Where(kv => kv.Key.IndexOf("Content") >= 0))
 {
  // Valuesはstring[]なのでstring.Joinを使って全要素を出力する
  Console.WriteLine($"{h.Key}: {string.Join(",", h.Values)}");
 }
}
// 出力(例)
// Content-Length: 1270
// Content-Type: text/html

「全部LINQで書きたいのにレガシーなコレクションにLINQで書けないものがあって嫌だ、という知り合いがいたんです。これは最初私はピンとこなかったんですが、しばらくして『なんでLINQで使えないんだ?』と実際に感じることが何度かあったので、レシピとして掲載することにしました。ちなみに、LINQをSQLのように書くのは実用性皆無の単なるデモ目的だと考えているので、プロのプログラマ用の本書では紹介していません」

第15章 ネットワークと通信

247 IPアドレスとMACアドレスを取得したい

取得手順

IPアドレスとMACアドレスはコンピューターのネットワークインターフェイス(仮想を含むネットワークアダプタ)に依存します。

 これを利用して次の手順でIPアドレスとMACアドレスを取得します。

  1. System.Net.NetworkInformation.NetworkInterfaceクラスのGetAllNetworkInterfaces静的メソッドを呼び出します。
  2. (1)が返したNetworkInterfaceオブジェクトの、配列の各要素について以下を実行します。この時、NetworkInterfaceTypeプロパティが、System.Net.NetworkInformation.NetworkInterfaceType列挙体のEthernet以外のものは無視します。
  3. GetIPPropertiesメソッドを呼び出してSystem.Net.NetworkInformation.IPInterfacePropertiesオブジェクトを取得します。
  4. a. IPv4のアドレスが必要な場合は、IPInterfacePropertiesオブジェクトのGetIPv4Propertiesメソッドを呼び出します。nullでなければIPv4の情報を保持しているのでUnicastAddressesプロパティ(System.Net.NetworkInformation.UnicastIPAddressInformationクラスのコレクション)を列挙します。
     b. IPv6のアドレスが必要な場合は、IPInterfacePropertiesオブジェクトのGetIPv6Propertiesメソッドを呼び出します。nullでなければIPv4の情報を保持しているのでUnicastAddressesプロパティ(System.Net.NetworkInformation.UnicastIPAddressInformationクラスのコレクション)を列挙します。
  5. UnicastIPAddressInformationオブジェクトのAddressプロパティを参照してSystem.Net.IPAddressクラスのオブジェクトを取得します。
  6. IPAddressオブジェクトのAddressFamilyプロパティを参照します。System.Net.Sockets.AddressFamily列挙体が返るので、IPv4アドレスが必要な場合はAddressFamily.InterNetworkのものを、IPv6アドレスが必要な場合はAddressFamily.InterNetworkV6のものを選択します。
  7. (6)で選択したIPAddressオブジェクトのToStringメソッドを呼び出して可読表現を入手します。
  8. MACアドレスを入手するには、(4)a.または(4)b.で選択されたNetworkInterfaceオブジェクトを利用します。
     a. GetPhysicalAddressメソッドを呼び出してSystem.Net.NetworkInformation.PhysicalAddressオブジェクトを取得します。
     b. PhysicalAddressオブジェクトのToStringメソッドを呼び出して可読表現を入手します。
コンピューターで利用可能なIPv4およびIPv6アドレスを表示する
// using System.Net.NetworkInformation; using System.Net.Sockets;が必要
// すべてのネットワークアダプタを列挙する
foreach (var intf in NetworkInterface.GetAllNetworkInterfaces()
 // イーサネットタイプのものだけを選択する
 .Where(itf => itf.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
 // 後続の処理が楽になるように必要な情報のみのオブジェクトを作成する
 .Select(itf => new { Name = itf.Name,
                      MacAddress = itf.GetPhysicalAddress(),
                      IPProperties = itf.GetIPProperties() })
 // IPアドレスを持つかどうかを判定する
 // GetIPv?Propertiesはそれぞれ返す型が異なるので本来は??では接続できない
 // ここではnullかどうかの判定なのでobjectにキャストして同時に判定している
 .Where(e => ((object)e.IPProperties.GetIPv4Properties()
           ?? (object)e.IPProperties.GetIPv6Properties()) != null))
{
 // アダプタ名とMACアドレスを出力する
 Console.WriteLine($"Name: {intf.Name}, MACAddress: {BitConverter.
ToString(intf.MacAddress.GetAddressBytes())}");
 // IPv4アドレスを表示する
 foreach (var address in intf.IPProperties.UnicastAddresses
  .Select(e => e.Address)
  .Where(e => e.AddressFamily == AddressFamily.InterNetwork))
 {
  Console.WriteLine($"IPv4 Address: {address}");
 }
 // IPv6アドレスを表示する
 foreach (var address in intf.IPProperties.UnicastAddresses
  .Select(e => e.Address)
  .Where(e => e.AddressFamily == AddressFamily.InterNetworkV6))
 {
  Console.WriteLine($"IPv6 Address: {address}");
 }
}
// 出力例
// Name: vEthernet (Hyper-V virtual interface), MACAddress: 00-xx-xx-xx-xx-xx
// IPv4 Address: 192.168.253.10
// IPv6 Address: xxxx::xxxx:xxxx:xxxx:xxxx%yy
GetUnicastAddressesメソッドを利用する方法

 .NET Framework 4以降であれば次のプログラムで示すように、利用可能なUnicastAddressInformationのコレクションをSystem.Net.NetworkInformation.IPGlobalPropertiesクラスのGetUnicastAddressesメソッドで取得できます。アダプタ情報等が不要な場合はこの方法が簡便です。

利用可能なIPv4アドレスを取得する(.NET Framework 4以降)
// using System.Net.NetworkInformation; using System.Net.Sockets; が必要
var address = IPGlobalProperties.GetIPGlobalProperties()
  .GetUnicastAddresses()
  // 以降の処理用にIPAddressオブジェクトの
  // 列挙に変換する
  .Select(e => e.Address)
  // IPv4にここでは限定する(IPv6アドレスが
  // 必要であれば条件を変える)
  .Where(e => e.AddressFamily == AddressFamily.InterNetwork)
  // この方法はループバックアドレス(127.0.0.1)が
  // 含まれるので除外する
  .Where(e => !IPAddress.IsLoopback(e))
  // マルチホームコンピューターの場合は他の条件も要考慮
  .First();
Console.WriteLine($"IPv4 Address: {address}");

「これは力作です。苦労してサンプルコードを書いたんですが、紹介しているように.NET Framework 4からは短いコードで実現できるんですよ。.NET Flameworkではよく使う機能はどんどん変化していくので、きちんとキャッチアップしていかないとたいへん損をします。新しいことを覚えて短く書くのが得なのか、いま知っている長いコードを以前のソースからコピペしてくるほうが省エネなのか、判断は難しいのですが、どこかで新しいものに更新していかないと、C#と付き合っている限り、今後もその古いコードを使ってしまいますからね。気がついたら知識もコードも更新していきましょう」

254 自己署名証明書を使っているWebサーバーにHTTPSでアクセスしたい

AuthenticationExceptionを独自に検証する方法

 HTTPSでは接続の開始時にサーバーの提示したサーバー証明書の正当性が検証されます。このときにサーバーが提示した証明書の正当性を検証できない場合(つまり、そのサーバーが正しい接続先だと認められない場合)、WebException(内容はAuthenticationException)がスローされて通信に失敗します。

 System.Net.ServicePointManagerクラスのServerCertificateValidationCallback静的イベントにコールバックを登録すると.NET Frameworkによる検証後に独自の検証処理を追加できます。

接続先ホストが提示した証明書のフィンガープリントが既知のものであれば接続を許可する
using System;
using System.IO;
using System.Net;
using System.Net.Security;
class VerifyTestCert
{
 // 既知のテスト証明書のフィンガープリント
 const string ExpectedFingerprint = "4F-F6-5E-E3-CE-B0-99-BD-68-6D-ADBA-EC-16-B8-D1-5C-30-75-6A";

 // 静的コンストラクターでServicePointMangerのコールバックを設定する
 static VerifyTestCert()
 { // ServerCertificateValidationCallbackは.NET Frameworkによる検証後に
   // 呼び出されて、検証結果をオーバーライドできる
   ServicePointManager.ServerCertificateValidationCallback =
    // certはX509Certifacteクラスのオブジェクト
    // chainはX509Chainクラスのオブジェクト
    // sslPolicyErrosはSslPolicyErrors列挙体
    // trueを返すと接続処理が続行される。falseを返すと
    // WebExceptionがスローされる
    (sender, cert, chain, sslPolicyErros) => {
     if (sslPolicyErros == SslPolicyErrors.None)
     { // .NET Frameworkによってポリシーエラーが検出されていなければOK
       return true;
     }
     // エラーであっても証明書のハッシュが既知のフィンガープリントと
     // 一致していればOKとする
     return ExpectedFingerprint == BitConverter.ToString(
      cert.GetCertHash());
   };
 }
 static void Main(string[] args)
 { // 引数で接続先を変える
  var request = WebRequest.CreateHttp(args[0]);
  request.UserAgent = "sample web client";
  request.Accept = "*/*";
  using (WebResponse response = request.GetResponse())
  using (var reader = new StreamReader(response.GetResponseStream()))
  {
   Console.WriteLine(reader.ReadToEnd());
  }
 }
}

「テストを行なう際、このレシピを知っておくと意外と便利です」

第16章 プロセスとスレッド

286 指定した時間処理を停止したい

Task.Delay静的メソッドを利用する

 連続して処理を実行することに問題があるため、一時的に処理を停止したいことがあります。

 たとえば、ウェブスクレイピングを行う場合には、連続してデータを取得するとサービス拒否攻撃とみなされる可能性があります。このため、人間がブラウジングするときと同様に数ページアクセスしたところで数秒の間隔を空けます。

 このような場合には、Task.Delay(ミリ秒)を利用します。Task.Delayは.NETFramework 4以降で有効なので、それより前のバージョンではThread.Sleep(ミリ秒)を利用して休止します。この2つのAPIの差は、Thread.Sleepが文字通り呼び出したスレッドそのものを休眠状態にするのに対し、Task.Delayは実行しているスレッドを別の処理に割り当て可能な状態とすることです。したがって、Task.Delayのほうがスレッドを占有しない分効率的です。

Webサーバーへのアクセス間隔を1秒おきにする
static async Task ReadFromWeb(string root)
{
 using (var client = new HttpClient())
 {
  foreach (var uri in GetLinks(root, await client.GetStringAsync(root)))
  {
   Console.Write(uri);
   // Webサーバーからuriを取得する
   var response = await client.GetAsync(uri);
   Console.WriteLine($"...{response.StatusCode}");
   await Task.Delay(1000); // 最低1秒間このスレッドを開放する
  }
 }
}

「この方法を知ってから、Thread.Sleepを書くときに手が震えます。これはリソースの無駄遣いなんだ、と(笑)」

第17章 例外処理

293 特定のプロパティ値を持つ例外だけをキャッチしたい(例外フィルターを使いたい)

catchとwhenを利用する

「catch (例外型 変数名)」に続けて「when (条件)」を記述すると、catch節で指定した例外の型で、かつwhen節で指定した条件に合致したcatchブロックが実行されます。

インサート結果によって例外を上位に送るかどうか決定する
try
{
 cmd.ExecuteNonQuery(); // insert tableの実行
 return InsertSucceeded;
}
catch (SQLException e) when (e.Number == 2601)
{
 return RetryByUpdate; // 呼び出したメソッドにupdate文を用意させる
}
// Numberプロパティが2601以外のSQLExceptionは上位へ伝播される

「C# 7から型に応じて処理を分岐するパターンマッチングが導入される予定と聞いたので、Elixirのパターンマッチングの書き方に似ていることもあり、catch whenの技術を流用して似たような構文で実現するのではないかと予測していたらちょっと外れてしまいました。でもcatchを使っている人ならすでにパターンマッチングの予習はできているので、安心してC# 7のパターンマッチングを使いこなせますね」

第18章 メタプログラミング

299 フィールド名を指定してフィールドにアクセスしたい

FieldInfoオブジェクトを取得し、GetValue/SetValueメソッドを利用する

 オブジェクトのフィールドを取得/設定するには、オブジェクトの型(Typeクラスのオブジェクト)のGetFieldメソッドを呼び出して得たFieldInfoオブジェクトのGetValue/SetValueメソッドを利用します。

フィールドにアクセスする
class Test
{
 public int counter = 3;
 internal string name = "abc";
 public static readonly string classDesc = "xyz";
}
...
var test = new Test();
// publicフィールドはType.GetFieldにフィールド名を与える
var counter = typeof(Test).GetField("counter");
// 非publicフィールドは第2引数にBindingFlags.NonPublic | BindingFlags.Instance
// を与える
var name = typeof(Test).GetField("name",
  BindingFlags.NonPublic | BindingFlags.Instance);
// フィールド値を取得するには、FieldInfo.GetValueに対象のオブジェクトを与える
Console.WriteLine($"{counter.GetValue(test)}, {name.GetValue(test)}");
// フィールドに値を設定するには、FieldInfo.SetValueに対象のオブジェクトと値を与える
counter.SetValue(test, 48);
name.SetValue(test, "ABC");
Console.WriteLine($"{test.counter}, {test.name}");
// publicフィールドはType.GetFieldにフィールド名を与える(静的フィールドかは問わない)
var classdesc = typeof(Test).GetField("classDesc");
// 静的フィールドのFieldInfo.GetValue / SetValueの第1引数はnullを与える
classdesc.SetValue(null, "XYZ");
// readonlyかどうかは関係しない
Console.WriteLine(Test.classDesc); // => XYZ (readonlyはコンパイル時の制約なので影響しない)

「前のレシピ(026)で、readonlyで修飾したフィールドは再代入ができないので安全だと言いながら、このレシピではreadonlyでも書き換えられると言っています(笑)。自分で自分の頭でも脚でも撃ち抜ける仕組みを持っている言語はプログラマを信頼している気持ちのよい言語ですね。それができない言語は気分的にも実用的にもいまいちです」

第19章 プログラム開発支援

306 デバッグ(トレース)出力をファイルに書き出したい

Listenersプロパティにリスナーを設定する

 Debugクラス、TraceクラスのWriteLine、WriteLineIfなどのメソッドの出力先は、各クラスのListenersプロパティに追加したリスナーによって決まります。

 既定のリスナーにはWindows APIのOutputDebugString関数を呼び出すDefaultTraceListenerが設定されています。

 この設定は、構成ファイル(.exe.config)で変更できます。

構成ファイルでトレース出力先ファイルを指定する
<configuration>
<system.diagnostics>
  <!-- 要素名はtraceだが、Debugクラスのリスナーも設定される -->
  <trace autoflush="false"> 
   <listeners>
    <remove name="Default"> 
    <!-- C:\temp¥traceOut.logに出力するTextWriterTraceListenerを設定
     name属性はなんでも良い(リスナーのNameプロパティに設定される) -->
    <add initializedata="c:¥temp¥traceOut.log" name="fileListener" type="System.Diagnostics.TextWriterTraceListener">
    <!-- コンソール(標準出力)へトレース出力するには以下の設定を利用する
     <add name="consoleListener" type="System.Diagnostics.ConsoleTraceListener"/>
    -->
   </add>
  </trace>
 </system.diagnostics>

「今回、かなりしっかり調べた結果が反映されているのがこのレシピです。こうやればいいのかと喜んでもらえる方が多いのではないでしょうか。私自身、これを見つけたときとても嬉しかったですね」

第20章 Windows環境

315 イベントログへ書き込みたい

イベントログへ出力する手順

 イベントログへ出力するには次の手順を取ります。

  1. 第1引数にアプリケーション名、第2引数にログファイル名を指定してCreateEventSource静的メソッドを呼び出してプログラムを登録します。第2引数をnullとすると、Applicationログへの出力となります。
  2. EventLogをnewしてオブジェクトを作成します。
  3. (2)で作成したEventLogオブジェクトのSourceプロパティに(1)で指定したアプリケーション名を設定します。
  4. (2)で作成したEventLogオブジェクトのWriteEntryメソッドを呼び出してログを出力します。

イベントログ出力手順についての注意点

 EventLog.CreateEventSourceおよび未登録時のSourceExistsの実行には管理者権限が必要です。これはアクセスに管理者権限が必要となるSecurityログを含むすべてのログを検索して、同じ名前が出現していないか確認するためです。そこで、ユーザー権限で実行するプログラムについては、上記の(1)の処理は他の処理とは切り離してインストーラーなどで提供したほうが良いでしょう。

 一度アプリケーション名を登録すると、以降はSecurityログ以外についてはユーザー権限で書き込みが可能です。

イベントログへ書き込む
// using System.Diagnostics; が必要
const int IOErrorEvent = 101; // EventID (任意の整数)
const short CatOperationFailed = 10;// カテゴリ(任意の整数)
...
if (!EventLog.SourceExists("EvLogTest")) // アプリケーション名が未登録ならば登録する
{ // Applicationログを利用する場合、第2引数はnullで良い。それ以外の場合はログ名を指定する
  EventLog.CreateEventSource("EvLogtest", null);
  // CreateEventSource静的メソッドは管理者権限でなければ実行できない
}
// ログの書き込み エラー発生はレアケースという前提でEventLogはusingで囲む
using (var log = new EventLog ( ))
{
  log.Source = "EvLogTest"; // CreateEventSourceで登録したアプリケーション名を設定する
  // 文字列のみを与えると情報レベルの書き込みとなる
  log.WriteEntry($"IO Error {errorNumber}");
  // EventLogEntryType列挙体で文字列とイベントログ種(エラー、警告、情報)を指定する
  log.WriteEntry($"IO Error {errorNumber}", EventLogEntryType.Error);
  // int型の第3引数でアプリケーション定義のイベント識別子を書き込める
  // 2引数以下の呼び出しでは0となる
  log.WriteEntry($"IO Error {errorNumber}", EventLogEntryType.Error,
          IOErrorEvent);
  // short型の第4引数でアプリケーション定義のログのカテゴリを指定できる
  // 3引数以下の呼び出しでは「なし」となる
  log.WriteEntry("start EvLogTest app", EventLogEntryType.Information,
          IOErrorEvent,CatOperationFailed);
}

「Win32APIを使ってイベントログを出力するのは意外と面倒なんです。メッセージのテンプレートを格納したDLLを用意して、あらかじめこれを使用するように登録しておかなければなりません。でないと、イベントログに『メッセージの詳細が見つかりません』というエラーメッセージが出てきます。ところが.NET Frameworkにはデフォルトのテンプレートが用意されているんですよ。これはすごく助かります。Win32APIの面倒さからイベントログを敬遠していると、.NET Frameworkではよろしくやってくれるようになっていると気づかないんですね」

便利になっていくC#をキャッチアップし続けることが大事

「C#はどんどん書きやすくなっているので、変化をキャッチアップしていってください。C# 1のときに覚えたもの以外は認めないというのは馬鹿らしいし、キャッチアップしなくてもきちんと動くからOKというのも違います。もし仕事で開発をしているなら、新しく入ってきた人の書き方についていけなくなるか、社内で決められている古い書き方をルールとして強制することになります。どちらも困りますよね。

artonさん
artonさん

 せめてC# 4までの書き方、つまりラムダ式を利用した書き方は理解して利用できるようになりましょう。非同期処理ならいきなりC# 5のasync/awaitから入るのもありですし、最新のC# 6はさらに細かく改善されています。きっとC# 7はさらによくなるでしょう。もちろん常に正しいわけではなく、指摘しようと思えばC# 2の匿名メソッドやC# 3のSQL構文のようにろくでもないものもあります。しかし、そういうものは匿名メソッドに対するラムダ式のように次のバージョンでちゃんと代替案が出てくるか、LINQのメソッド形式+ラムダ式のように最初からあるべきものも用意されています。どちらもラムダ式ですね。同じように本書でTupleが便利だと思って覚えても、C# 7が登場したときTupleが更新されていれば本書で得た知識は古いバージョン専用として切り捨てる、そういう考え方ができるようになってもらいたいですね」

C#逆引きレシピ

Amazon   SEshop   その他

C#逆引きレシピ

著者:arton
発売日:2016年6月9日(木)
価格:3,132円(税込)

本書について

本書は開発の現場で活躍する著者が、C#を利用した開発時に役立つTIPSをまとめた書籍です。実際の現場で「さっ」とひけるよう、利用頻度の高いカテゴリ別に分け、実際の開発現場で使えるTIPSをふんだんに用意しています。

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/9444 2016/06/16 08:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング