2014年10月24日

コレクション系インターフェース 早見表

コレクション系インターフェースの型パラメータの共変性、および、実装されるメソッド、プロパティの早見表。
※IReadOnly~ は .NET 4.5 から導入
※非ジェネリックはもう使わないと思うので省略

共変 <out T>
メソッド プロパティ
GetEnumerator()
Add( T )
Clear()
Contains( T )
CopyTo( T[], int )
Remove( T )
IndexOf( T )
Insert( int, T )
RemoveAt( int )
Count { get; }
IsReadOnly { get; }
this[ int ] { get; }
this[ int ] { set; }
IEnumerable<T>
ICollection<T>
IList<T>
IReadOnlyCollection<T>
IReadOnlyList<T>

メソッド プロパティ
Add( TKey, TValue )
ContainsKey( TKey )
Remove( TKey )
TryGetValue( TKey, out TValue )
this[ TKey ] { get; }
this[ TKey ] { set; }
Keys { get; }
Values { get; }
IDictionary
<TKey, TValue>
他の項目は ICollection<T> 参照
Keys と Values は ICollection<T>
IReadOnlyDictionary
<TKey, TValue>
他の項目は IReadOnlyCollection<T> 参照
Keys と Values は IEnumerable<T>

雑感

元々あまり使われなかっただろう IsReadOnly プロパティは、IReadOnly~ インターフェースが導入されたため、完全に要らない子に・・・

IReadOnlyDictionary の Keys/Values プロパティの戻り値が IReadOnlyCollection<T> ではなく IEnumerable<T> なのは、Count プロパティの使用頻度と実装のし易さあたりが理由?
確かに Keys や Values の Count プロパティなんてほぼ使わないだろうし、IEnumerable<T> だと yield で簡単実装できるから、いい判断だと思う。
逆に、IDictionary の Keys/Values プロパティの戻り値 ICollection<T> は機能が過剰だし、実装も面倒。

.NET 4.5 からは IReadOnly~ を積極的に使っていこう・・・かな

2014年10月10日

SQL Server IDENTITY 列 1 個のテーブルの更新方法

ID 生成専用として、IDENTITY 列 1 個だけのテーブルを用意したりする。

CREATE TABLE IdGenerator (
  Id int IDENTITY(1,1)
);

これを更新(INSERT)する方法。

INSERT INTO IdGenerator DEFAULT VALUES;

-- 同時に id を取得するなら
INSERT INTO IdGenerator OUTPUT inserted.Id DEFAULT VALUES;

INSERT なので、1 列だけだがデータは溜まっていく。
気になる場合は、INSERT と同時に消すか、削除バッチなどで。

参考URL

2014年10月3日

Dictionary<TKey, TValue> の実装について

Dictionary<TKey, TValue> の実装について、ソースからの説明。
知ってるといいことあるかもしれない・・・

Dictionary の本体


// Dictionary の要素の構造体
// KeyValuePair をそのまま保持しているわけではない
private struct Entry {
    public int hashCode;  // 正の数にしたハッシュコード(0~7FFFFFFF)、-1 は未使用
    public int next;      // buckets 位置が同じ要素の次インデックス、-1 は次の要素無し
    public TKey key;      // キー
    public TValue value;  // 値
}

// ハッシュコード -> entries インデックスのマップ
// 実際は、正のハッシュコード % buckets.Length で buckets のサイズに合わせる
private int[] buckets;

// 要素の配列
private Entry[] entries;

ソースだけだとピンとこないかもしれないので、追加処理を図で。
Dictionary Add image
IEqualityComparer<TKey>.GetHashCode() でキーからハッシュコードを求める
0x7FFFFFFF との論理積で、ハッシュコードを正の数にする

ハッシュコードを buckets 位置 B に変換(剰余を求める)

buckets[B] に entries の次の未使用インデックス E を設定

entries[E] に hashCode, key, value を設定

ちなみに、ハッシュコードの衝突(buckets 位置が使用済)が起こった場合。
Dictionary Add image (collision)
buckets 位置 B を求める

buckets[B] に entries の次の未使用インデックス E を設定

entries[E] に hashCode, key, value を設定、next に buckets[B] の元の値を設定

Dictionary の容量

ハッシュテーブルの容量については素数派と 2^n 派が存在するが、Dictionary では素数を採用している。
※素数のメリットはハッシュ関数が多少ダメでも衝突が起こりにくいこと、2^n のメリットは剰余の計算が速いことらしい。

Dictionary で採用される素数については、HashTable ソースの HashHelpers.primes 参照。
HashHelpers.primes 以降の素数は、その場で計算される。(HashHelpers.GetPrime 参照)

Dictionary コンストラクタの capacity 引数で素数以外を指定しても、それ以上の素数に切り上げられる。
容量拡張が発生するタイミングは、entries を使い切った場合で、現在の容量×2 以上の素数に切り上げられる。
(buckets も同時に拡張される)

Dictionary の列挙

entries の順序どおり列挙される。
entries は先頭から埋められていくので、Add とインデクサによる追加・変更のみならば、Dictionary への追加順で列挙される。

Remove による削除が発生した場合、次の追加処理では、削除された領域を先に使うため、追加順による列挙ではなくなる。
※複数削除の場合は、Entry.next を利用して空き領域リストが構築される

[Remove → Add 後の列挙]

static class TestDic
{
    static void Main()
    {
        var dic = new Dictionary<string, int>();
        var val = 0;
        foreach( var c in "abc" ) dic.Add(c.ToString(), val++);
        Console.WriteLine("ADD a b c");
        Console.WriteLine(string.Join(" ", dic));

        dic.Remove("b");
        Console.WriteLine("REMOVE b");
        Console.WriteLine(string.Join(" ", dic));

        dic.Add("d", val++);
        Console.WriteLine("ADD d");
        Console.WriteLine(string.Join(" ", dic));
    }
}
[結果]
ADD a b c
[a, 0] [b, 1] [c, 2]
REMOVE b
[a, 0] [c, 2]
ADD d
[a, 0] [d, 3] [c, 2]

また、列挙時に追加・変更・削除が検知できるよう、version メンバ変数がある。
追加・変更・削除が発生した場合は version をインクリメントして、IEnumerator の MoveNext 時に version が変わっていたら、InvalidOperationException を発生させる。

IEqualityComparer<TKey>

キーのハッシュコードの計算や一致比較を担当する IEqualityComparer<TKey> は、コンストラクタの comparer 引数で指定することができる。
未指定や null を渡した場合は EqualityComparer<TKey>.Default が使用される。
MSDN にある「既定の等値比較子」がこれに該当する。

int や string など基本的な型は EqualityComparer<TKey>.Default で問題ないが、配列や自前のクラスなどは IEqualityComparer<TKey> を自分で用意する必要がある。
※EqualityComparer<T>.Default については、いつか説明予定・・・

ちなみに、値を担当する IEqualityComparer<TValue> はコンストラクタで指定できないため、問答無用で EqualityComparer<TValue>.Default が使用される。
実際に使用されているメソッドは下記。
  • ContainsValue
  • ICollection<KeyValuePair<TKey, TValue>>.Contains
  • ICollection<KeyValuePair<TKey, TValue>>.Remove

EqualityComparer<TValue>.Default で正確な一致比較ができない場合は、これらを使用しない方がいい。

2014年9月25日

System.Collections.ObjectModel.Collection<T> について

System.Collections.ObjectModel にある Collection<T> について、ソース観点からの補足説明のようなもの。

主な用途は、継承用クラスになる。
実際、System.Collections.ObjectModel にある他クラスの継承元として使われている。
単品で使用できないこともないが、IList<T> ないし List<T> の単なるラッパになるので(Collection<T> の引数無しコンストラクタは内部で List<T> を生成)、ラップするメリットがあまりない。
List<T> の機能を制限したいなら、基本的にインターフェースへのキャストで十分。

状態変更時に挟める protected メソッドとして、ClearItemsInsertItemRemoveItemSetItem が用意されている。
これらと状態変更用の public メソッド・プロパティとの関係は下記
public protected protectedの実装 備考
Item
(インデクサ)
SetItem IList[index] = item protected メソッド実装時、index 引数の境界値チェックは不要。(public 側で実施済)
Add InsertItem IList.Insert(index, item)
Insert
Remove RemoveItem IList.RemoveAt(index)
RemoveAt
Clear ClearItems IList.Clear()

注意すべきは、Add と Insert で同じ InsertItem メソッドが使われる点。
Add(item) は Insert(Length - 1, item) として処理される。

表でも自明なように IList.Add は使用されない。
自前の IList のラッパーとする場合、IList.Add のみの特殊処理を実装していても使われないので注意。
(普通は IList.Insert と差がある実装にしないだろうけど・・・)

2014年9月24日

NuGet のリポジトリパスの変更

NuGet のリポジトリパス(パッケージがダウンロードされる場所)は、デフォルトだと次である。
$(ソリューションディレクトリ)\packages\

ソリューション 1 つだとデフォルトのままでも問題ないのだが、複数だとソリューション毎にリポジトリパスが存在し、それぞれパッケージがダウンロードされることになる。

・・・同じパッケージが違うディレクトリにダウンロードされるのって微妙じゃね?
ということが考慮されてかは分からないが、リポジトリパスは nuget.config を書くことで任意のパスを指定できる。

[リポジトリパス指定用の nuget.config 記述例]

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <config>
    <add key="repositoryPath" value="リポジトリパス(絶対or相対パス)" />
  </config>
</configuration>

ちなみに nuget.config の配置場所。
ソリューション毎 $(ソリューションディレクトリ)\nuget.config
共通(ユーザー毎) %APPDATA%\NuGet\NuGet.Config

nuget.config の他の設定項目。

2014年9月11日

配列のなんちゃって共変性

共変性

共変性とは、要は暗黙の型変換を容易にするための機能である。
参照型の型 A が型 B へ暗黙の型変換が可能な場合に、ジェネリックインターフェースの共変な型パラメータ(out 付き型パラメータ)に型 A を指定した If<A> は同じインタフェースの If<B> へ暗黙の型変換が可能になる。
※out が付いた型パラメータは出力(メソッドの戻り値、読取専用プロパティの型)にしか使えなくなる。
   出力用の out 引数には使えない。→理由

インタフェースの他に、デリゲートの型パラメータも共変にすることができる。

[共変性のサンプル]

class A : B { } // B はクラスまたはインターフェース

static class TestCovariance
{
    static void Main(string[] args)
    {
        // 暗黙の型変換
        A a = new A();
        B b = a;

        // 共変のインターフェース IEnumerable<out T>
        IEnumerable<A> aList = new List<A>();
        IEnumerable<B> bList = aList;

        // 共変のデリゲート Func<out TResult>
        Func<A> aFunc = () => new A();
        Func<B> bFunc = aFunc;
    }
}

配列の共変性

C# 及び VB の配列は共変性がある。

共変性があるのに、不変な型パラメータを持つ ICollection<T>IList<T> を実装している。
※普通のコードでは実装できないため、実行時に提供するという特殊なやり方で。
これはジェネリック(.NET 2.0~)が配列(.NET 1.0~)より後に導入されたためでもあるのだが、そもそもイミュータブルでない配列を共変にしているのがおかしな話なのである。

MSDN にも書いてある話なのだが、配列を暗黙の型変換することでタイプセーフではなくなる。
例えば、次のコードはコンパイルできるが、実行時エラー ArrayTypeMismatchException が発生する。

static class TestCovariance
{
    static void Main(string[] args)
    {
        string[] strings = {"1"};
        object[] objects = strings;
        objects[0] = 1; // ここで例外発生
    }
}

IReadOnlyList<T>

上記の気持ち悪さを解決するためかは分からないが、.NET 4.5 からイミュータブルな配列を扱える IReadOnlyList<T> が導入されている。

static class TestCovariance
{
    static void Main(string[] args)
    {
        // Array.AsReadOnly の戻り値は ReadOnlyCollection<T>
        // このメソッドは .NET 2.0 からあったが IReadOnlyList<T> が存在しなかった
        IReadOnlyList<string> strings = Array.AsReadOnly(new[]{"1"});
        IReadOnlyList<object> objects = strings;
        objects[0] = 1; // コンパイル NG
    }
}

.NET 4.0 までの資産は IList<T> や ICollection<T> 前提で作ってあることが多いので、今更導入されてもという気がするのだが、よりタイプセーフに実装できるということで・・・

参考URL

2014年9月2日

コンパイラをごまかすキャスト

※実質、.net - C# non-boxing conversion of generic enum to int? - Stack Overflow の紹介記事です。

ジェネリック型パラメータと Enum の相性は悪い。
型パラメータの制約として、Enum クラスを指定できない。
Enum に対する制約は、せいぜい下記のようにしかできない。(ゆるい制約だとstructだけの場合も)
[サンプル1 : Enum に対する制約]

public static void HandleEnum<TEnum>(TEnum value)
    where TEnum : struct, IComparable, IFormattable, IConvertible
{
}

問題は、型パラメータで受け取った Enum 値を int などにキャストする場合である。
[サンプル1]で value を int へキャストしようとすると、コンパイラに怒られる。
TEnum の制約では、int へ変換可能かどうかを判断できないためである。
int type conversion error

object キャスト

これを解決する最も簡単な方法は、一度 object へキャストすることである。

public static void HandleEnum<TEnum>(TEnum value)
    where TEnum : struct, IComparable, IFormattable, IConvertible
{
    var i = (int)(object)value;
}

ただし、この方法はコードの通りボックス化(ボクシング)→ボックス化解除(アンボクシング)であるため、あまりパフォーマンス的によろしくない。

動的ラムダ式でキャスト

ボックス化を回避できる方法として、式木で動的ラムダ式を構築してキャストする方法がある。
このコードを少し変更(Get メソッドの処理を静的コンストラクタに移動)。

public static class CastTo<T>
{
    private static class Cache<S>
    {
        static Cache()
        {
            // 次のラムダ式を式木で構築
            // (S s) => (T)s
            var p = Expression.Parameter(typeof(S));
            var c = Expression.ConvertChecked(p, typeof(T));
            Caster = Expression.Lambda<Func<S, T>>(c, p).Compile();
        }
        internal static readonly Func<S, T> Caster;
    }

    public static T From<S>(S source)
    {
        return Cache<S>.Caster(source);
    }
}
[使用例]

public static void HandleEnum<TEnum>(TEnum value)
    where TEnum : struct, IComparable, IFormattable, IConvertible
{
    var i = CastTo<int>.From(value);
}

Expression.ConvertChecked でキャストするだけのラムダ式を構築している。
Expression.ConvertChecked を使うことで、コンパイラのキャスト可能チェックをスルーできる。
ちなみにオーバーフロー例外を発生させたくない場合は Expression.Convert を使う。

動的IL でキャスト

ここにもあるのだが、キャスト処理を動的IL で構築することで、ボックス化を回避できる。

public static class IlCastToInt32<T>
{
    static IlCastToInt32()
    {
        var method = new DynamicMethod(
            "IlCastToInt32Caster", typeof(int), new[] { typeof(T) });
        var il = method.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Conv_Ovf_I4);
        il.Emit(OpCodes.Ret);
        Caster = (Func<T, int>)method.CreateDelegate(typeof(Func<T, int>));
    }
    public static readonly Func<T, int> Caster;
}

// クラスの型パラメータを省略するための拡張メソッド
public static class IlCastExtensions
{
    public static int CastToInt32<T>(this T value)
    {
        return IlCastToInt32<T>.Caster(value);
    }
}
[使用例]

public static void HandleEnum<TEnum>(TEnum value)
    where TEnum : struct, IComparable, IFormattable, IConvertible
{
    var i = value.CastToInt32();
}

戻り値を決め打ちする必要があるため、動的ラムダ式より汎用性が下がる。
その他の型に変換したい場合は、OpCodes を参照。
また、型パラメータとして渡すもの(ここでは Enum)は public である必要がある。
public でないと TypeAccessException が発生する。

パフォーマンス

[測定コード]

static class TestEnumCast
{
    static void Main(string[] args)
    {
        var flag = int.Parse(args[0]); // 1 ~ 5
        var loopCount = int.Parse(args[1]);
        var source = MuseEnumerable().Take(loopCount).ToArray();
        var temp = new List<int>(loopCount);

        if( flag > 3 ){
            // 4,5 はイニシャルコスト無しの設定
            // -> 一度実行してキャッシュを作成しておく
            flag -= 2;
            CastTo<int>.From(Muse.None);
            Muse.None.CastToInt32();
        }

        var sw = new Stopwatch();
        sw.Start();
        switch( flag ){
        case 1:
            foreach( var item in source ) temp.Add((int)(object)item);
            break;
        case 2:
            foreach( var item in source ) temp.Add(CastTo<int>.From(item));
            break;
        case 3:
            // 拡張メソッドだとメソッド 1 個分のコストがかかるため、直呼び
            foreach( var item in source ) temp.Add(IlCastToInt32<Muse>.Caster(item));
            break;
        }
        sw.Stop();
        Console.WriteLine(sw.Elapsed);
    }

    static IEnumerable<Muse> MuseEnumerable()
    {
        while( true ){
            yield return Muse.Honoka;
            yield return Muse.Kotori;
            yield return Muse.Umi;
        }
    }
}

[Flags]
public enum Muse
{
    None = 0x0,
    Honoka = 0x1,
    Kotori = 0x2,
    Umi = 0x4
}

[結果]
loopCount object 動的ラムダ式 動的IL
イニシャルコスト有 10000 0.0003435 0.0053873 0.0031249
イニシャルコスト無 0.0001409 0.0001416
イニシャルコスト有 100000 0.0035345 0.0067660 0.0044378
イニシャルコスト無 0.0014422 0.0014731
※コマンドプロンプトで実施(csc /optimize)
※10回の算術平均、単位は[s]

結果をまとめると下記。(左の方が高性能)
キャスト : 動的ラムダ式 ≒ 動的IL > object
イニシャルコスト : 動的IL > 動的ラムダ式

キャスト処理に関しては、ボックス化を回避することで 2 倍以上の性能になる。
動的ラムダ式と動的IL で差はほぼないため、
汎用性や型パラメータの public 制限を考慮すると動的ラムダ式、イニシャルコストが少ない方がいいなら動的IL となる。
また、動的IL は拡張メソッドから呼び出すと、わずかだがその分のコストがかかる。

ただ、イニシャルコスト >>> object キャスト 1 回 なので、条件によっては無意味な工夫になる。

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0
Intel(R) Celeron(R) CPU G530 @ 2.40GHz/DDR3 8GB(4GB x 2)

2014年8月25日

メソッドの型推論で型パラメータの制約は使われない

[サンプルコード]

static class TestTypeInference
{
    static void Main()
    {
        // コンパイル NG
        DoSomething(new Dictionary<string, int[]>());

        // コンパイル OK
        DoSomething<int[], int>(new Dictionary<string, int[]>());
    }

    static void DoSomething<TCollection, TItem>(IDictionary<string, TCollection> map)
        where TCollection : ICollection<TItem>
    {
        Console.WriteLine("DoSomething");
    }
}

DoSomething メソッドは、map 引数の型から TCollection は推論できるが、TItem は推論できない。
TCollection の制約から分かりそうなもんだが、推論してくれない。

あきらめて型パラメータを指定してあげましょう、という話。

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

参考URL

2014年8月22日

4Byte 文字を含んだ文字列の文字列挙

普通、文字列を foreach で回すだけで文字列挙は可能だが、4Byte 文字が混じっていると話が違ってくる。
4Byte 文字はその名のとおり 4Byte なので char 型 2 個分に相当する。
※char 型 は 2Byte、.NET の内部文字コードは UTF-16

[文字列挙サンプル]

static void Main()
{
    var text = "鮭𩸽"; // さけ(2Byte) ほっけ(4Byte)
    Console.WriteLine(text.Length);
    foreach( var c in text ) Console.WriteLine(c);
}
[結果]
3
鮭
�
�
※UTF-8 コードページによる表示なので、実行環境は PowerShell ISE 推奨

StringInfo

では、4Byte 文字も 1 文字として扱いたい場合なのだが、StringInfo が用意されている。
列挙に関しては、StringInfo.GetTextElementEnumerator があるが、TextElementEnumerator を返すため、少々使い勝手が悪い。
そこで、次の構造体(とおまけの拡張メソッド)を作成。

public struct TextElementEnumerable : IEnumerable
{
    public string Source;

    public TextElementEnumerator GetEnumerator()
    {
        return StringInfo.GetTextElementEnumerator(Source);
    }
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

    public static TextElementEnumerable Create(string source)
    {
        return new TextElementEnumerable() { Source = source };
    }
}

// TextElementEnumerator が IEnumerator しか実装していないため、
// IEnumerable<string> が欲しい場合はキャストが必要
// ということを考慮した拡張メソッド
public static class TextExtensions
{
    public static IEnumerable<string> GetTextElementEnumerable(this string source)
    {
        if( source == null ) throw new ArgumentNullException("source");
        return TextElementEnumerable.Create(source).Cast<string>();
    }
}

[使用例]

static void Main()
{
    var text = "鮭𩸽"; // さけ(2Byte) ほっけ(4Byte)
    // 4Byte 文字を考慮した文字数
    Console.WriteLine(new StringInfo(text).LengthInTextElements);
    // 4Byte 文字を考慮した文字列挙
    foreach( var c in text.GetTextElementEnumerable() ) Console.WriteLine(c);
}
[結果]
2
鮭
𩸽

参考URL

2014年8月13日

自作シリアライズの注意事項 #1 コレクション

ISerializable で自作シリアライズを実装する際、デシリアライズ処理におけるコレクション(配列やList)の扱いには注意が必要である。
コレクションのデシリアライズ直後、中身はまだデシリアライズされてない。

[ISerializable サンプル]
※本来、これらのプロパティだけなら ISerializable の実装は不要

[Serializable]
public class Node<T> : ISerializable
{
    public Node(){}
    public T Value;
    public readonly List<Node<T>> Children = new List<Node<T>>();

    private Node(SerializationInfo info, StreamingContext context)
    {
        Value = (T)info.GetValue("Value", typeof(T));
        Children = (List<Node<T>>)info.GetValue("Children", typeof(List<Node<T>>));

        Console.WriteLine("コレクションの個数 : {0}", Children.Count);
        foreach( var child in Children ) Console.WriteLine(child == null);
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Value", Value);
        info.AddValue("Children", Children);
    }
}

上記オブジェクトをシリアライズ→デシリアライズしてみる。

static class TestSerialization
{
    static void Main()
    {
        var node = new Node<string>() { Value = "parent" };
        node.Children.Add(new Node<string>() { Value = "one" });
        node.Children.Add(new Node<string>() { Value = "two" });

        var mem = new MemoryStream();
        var formatter = new BinaryFormatter();
        formatter.Serialize(mem, node);

        mem.Position = 0;
        var deserialized = (Node<string>)formatter.Deserialize(mem);
        Console.WriteLine("デシリアライズ後");
        Console.WriteLine(deserialized.Children[0].Value);
        Console.WriteLine(deserialized.Children[1].Value);
    }
}

結果は、
コレクションの個数 : 2
True
True
コレクションの個数 : 0
コレクションの個数 : 0
デシリアライズ後
one
two

コレクションのデシリアライズ直後は、中身が null であることが分かる。
その後、コレクションの中身のデシリアライズが実行されている。
つまり、コレクションのデシリアライズは、コレクション本体→中身の順で行われる。

OnDeserializedAttribute

デシリアライズ処理において、コレクションの中身にいじりたい場合はどうするのか?
例えばシリアライズ対象外のプロパティ(Parentとか)を設定したり、独自のAddメソッドなどで追加したい場合など。

そのために、OnDeserializedAttribute が存在する。
これを付けたメソッドは、コレクションの中身がデシリアライズされた後に呼び出される。

[OnDeserialized を追加した ISerializable サンプル]

[Serializable]
public class Node<T> : ISerializable
{
    public Node(){}
    public T Value;
    public readonly List<Node<T>> Children = new List<Node<T>>();

    private Node(SerializationInfo info, StreamingContext context)
    {
        Value = (T)info.GetValue("Value", typeof(T));
        Children = (List<Node<T>>)info.GetValue("Children", typeof(List<Node<T>>));

        Console.WriteLine("コレクションの個数 : {0}", Children.Count);
        foreach( var child in Children ) Console.WriteLine(child == null);
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        Console.WriteLine("OnDeserialized : Value={0}", Value);
        foreach( var child in Children ) Console.WriteLine(child.Value);
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Value", Value);
        info.AddValue("Children", Children);
    }
}

上と同じ方法でシリアライズ→デシリアライズした結果は、
コレクションの個数 : 2
True
True
コレクションの個数 : 0
コレクションの個数 : 0
OnDeserialized : Value=parent
one
two
OnDeserialized : Value=one
OnDeserialized : Value=two
デシリアライズ後
one
two

コレクションの中身がデシリアライズされているので、変更が可能。

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

参考URL

2014年7月28日

javascript 引数の無名関数内のみで使える関数

Jaml などのHTML ジェネレータを調べてる際に気付いたことだが、javascript では、引数に渡す無名関数内のみに関数を公開する方法がある。

[Jaml のサンプルコード]

Jaml.register('sample', function(){
    div(
        h1('サンプル'),
        p('Jaml です。'),
        br()
    );
});

上記サンプルの無名関数内で呼び出している div や h1 などの関数は、外部に公開されていない(グローバル関数ではない)。
どうやって実現しているのかさっぱり分からなかったので、Jaml のソースを拝見。
div や h1 は Jaml.Template の prototype 関数で、with と eval で実現できる模様。

[実現コード]

//[定義]
var Test = {
    console : function(callback){
        var logger = new Logger();
        //・callback を文字列展開して eval 実行することで、
        //  callback の変数スコープをこの関数内に変更
        //・with を使って prototype 関数に直接アクセス
        with( logger ) eval('(' + callback.toString() + ').call(logger)');
    }
};
var Logger = function(){ this.count = 0; };
Logger.prototype = {
    log: function(message){
        console.log(message);
        this.count++;
    },
};

//[実行]
Test.console(function(){
    log('one');
    log('two');
    log(count);
});

//[結果]
// one
// two
// 2

prototype 関数でなければ with で囲む必要はない。
上記例で、Test.console 内に定義した関数なら、外部に公開されず、引数の callback 内でのみ使える関数となる。

おまけ

本題とはあんまり関係ないけど、Jaml から eval と with を除いたもの。
Jaml.register に渡す無名関数の第一引数に Jaml.Template インスタンスが渡されるので、そこから div や h1 などのメソッドを使う。

2014年7月13日

オーバーロードの比較

比較といっても大きな違いは 1 つだけ。
C# は同じ型の引数でも、値渡しと参照渡し(ref または out)で別々のメソッドとして定義できるが、VB は不可。

具体的には、C# で次はコンパイル可。

public class TestOverloadCs
{
    public static void Action(int value)
    {
        Console.WriteLine("Action(int)");
    }
    public static void Action(ref int value)
    {
        Console.WriteLine("Action(ref int)");
    }
}

VB で次はコンパイル不可。

Public Class TestOverloadVb
    Public Shared Sub Action(ByVal value As Integer) 'コンパイルエラー
        Console.WriteLine("Action(int)")
    End Sub

    Public Shared Sub Action(ByRef value As Integer)
        Console.WriteLine("Action(ref int)")
    End Sub
End Class

VB は呼び出し側で ref とか out とかのキーワードを付けないから、上記のようなオーバーロードは禁止されているのだと思われる。

また、上記の C# のオーバーロードは、VB から普通に呼べないので注意。
VB_OverloadMethodCallError
リフレクションを使うとたぶん呼べる。

VB で使用するライブラリを C# で書く場合、上記のようなオーバーロードは避けるのが無難。
C# と VB のちゃんぽんはオススメしないが・・・

参考URL

2014年7月12日

SortedSet<T> コンストラクタのバグ (.NET4~4.5)

次のような拡張メソッドを作った際に気付いたのだが、SortedSet<T> のコンストラクタ (IEnumerable<T>, IComparer<T>) に (要素 2 個以上のコレクション, null) を渡すと例外が発生する。
引数 1 個の場合や、要素 1 個のコレクションでは例外にならないから分かりにくい。

public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source, IComparer<T> comparer)
{
    if( source == null ) throw new ArgumentNullException("source");
    return new SortedSet<T>(source, comparer);
}
public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source)
{
    return source.ToSortedSet(null);
}

[検証コード]

static void Main()
{
    var test1 = new SortedSet<int>(new[]{ 1 }, null);
    Console.WriteLine(test1.Count);
    var test2 = new SortedSet<int>(new[]{ 1, 2 }, null);
    Console.WriteLine(test2.Count);
}
[結果]
1

ハンドルされていない例外: System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
   場所 System.Collections.Generic.SortedSet`1..ctor(IEnumerable`1 collection, IComparer`1 comparer)

   場所 Program.Main()

原因は、SortedSet<T> のソース (Date: August 15, 2008) を見ると明らかで、168行目の comparer に this. の付け忘れ…
ちなみに、似たようなクラスである HashSet<T> は問題なし。

バグを考慮した拡張メソッド

そのうち修正されることを期待しつつ、拡張メソッドは次のように修正。

public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source, IComparer<T> comparer)
{
    if( source == null ) throw new ArgumentNullException("source");
    return new SortedSet<T>(source, comparer ?? Comparer<T>.Default;);
}
public static SortedSet<T> ToSortedSet<T>(this IEnumerable<T> source)
{
    return source.ToSortedSet(null);
}

C# 6.0 からコンストラクタの型引数を推論してくれるようになるらしいんで、こんなメソッドはいらなくなるはず。

2014年6月26日

Task 成功時のみ ContinueWith

TaskTask<TResult> の成功(完了)時のみ ContinueWith させたいと思ったことはないだろうか。
下記パターンとか・・・
Task(1つ目の処理)
.ContinueWith(成功時の処理)
.ContinueWith(成功時の処理)
・・・
.ContinueWith(エラーをまとめて処理)

TaskContinuationOptions.OnlyOnRanToCompletion を ContinueWith の引数に入れてやれば、成功時のみは可能なのだが、ContinueWith のメソッドチェーンで実装が難しくなる。

var task = Task.Factory.StartNew(() => {
    Console.WriteLine("1st task");
    /* 略 */
    return 0;
}).ContinueWith(t => {
    // 1st task 成功時のみ
    Console.WriteLine("2nd task");
    /* 略 */
    return 0;
}, TaskContinuationOptions.OnlyOnRanToCompletion).ContinueWith(t => {
    // 前 task の続き(つまり、1st task 成功時のみ)
    Console.WriteLine("3rd task");
    /* 略 */
    return t.Result;
});

上記は、1st task 成功時のみ 2nd task → 3rd task の順で実行される。
1st task で例外が起こった場合、以降の Task は実行されず、
例外も未処理になってしまうため、task が GC される際にファイナライザで例外が発生する。(Webアプリとかで発生するとアプリが死ぬ奴)
※Task のエラー未処理対策は TaskScheduler.UnobservedTaskException

正しい方法としては、最初の Task を一度変数に格納して、そこから成功時・失敗時で別の Task に分離させていく。
→ 変数が増えてコードが複雑になる

では、ContinueWith 内で Task.IsFaulted なら Task.Exception を投げればいいかも?となるかもしれないが、ContinueWith 内に毎回同じ処理を書くのは面倒。
というわけで、次の拡張メソッドを作成。

解決案


/// 
/// task 成功時のみ completionFunc を実行する。
/// エラー・キャンセル情報は、次の Task に渡される。
/// beforeHandler が指定された(null でない)場合、task の Status に関わらず、最初に実行される。
/// 
public static Task<TOut> OnSuccess<TIn, TOut>(
    this Task<TIn> task, Func<TIn, TOut> completionFunc, Action<Task<TIn>> beforeHandler)
{
    return task.ContinueWith(t => t.OnSuccessImpl(completionFunc, beforeHandler))
        .Unwrap();
}

/// 
/// OnSuccess の実装。
/// Task 内で OnSuccess を使う場合は、こちらを使用すると Task ネストが深くなりにくい。
/// 
public static Task<TOut> OnSuccessImpl<TIn, TOut>(
    this Task<TIn> task, Func<TIn, TOut> completionFunc, Action<Task<TIn>> beforeHandler)
{
    if( beforeHandler != null ) beforeHandler(task);
    var tcs = new TaskCompletionSource<TOut>();
    switch( task.Status ){
    case TaskStatus.Canceled:
        tcs.TrySetCanceled();
        break;
    case TaskStatus.Faulted:
        tcs.TrySetException(task.Exception.InnerExceptions);
        break;
    case TaskStatus.RanToCompletion:
        TOut result;
        try {
            result = completionFunc(task.Result);
        } catch( Exception ex ){
            tcs.TrySetException(ex);
            break;
        }
        tcs.TrySetResult(result);
        break;
    }
    return tcs.Task;
}
[使用例]

var task = Task.Factory.StartNew(() => {
    Console.WriteLine("1st task");
    var len = ((string)null).Length; //例外発生
    return 0;
}).OnSuccess(t => {
    // 1st task 成功時のみ
    Console.WriteLine("success task");
    return 0;
}, null).ContinueWith(t => {
    // 必ず実行
    Console.WriteLine("error handling");
    return t.Result;
});
Console.WriteLine(task.Result);

//[結果]
// 1st task
// error handling
//
// 1st task の例外メッセージ

また、このメソッドはエラーを次の Task に流すため、次の効果がある。
  • エラー未処理バグが減る
  • AggregateException が深くならない

2014年6月21日

構造体(struct)で再帰的定義

.NET の構造体は、自身をプロパティとして持つことはできない。(クラスなら可能)
理由は単純で、構造体は値型で null にならないため、構造体のサイズが無限になってしまうためである。
(自身をプロパティに持つと、そのプロパティがさらに自身をプロパティとして持ち・・・と無限に続く)

// 自身をプロパティとして持つ例
// ※コンパイルエラーになる
struct Node
{
    public int Value;
    public Node Next;
}

代替案1 クラス

構造体の代わりにクラスを使う。
クラスは null になるため、再帰的定義が可能。

class Node
{
    public int Value;
    public Node Next;
}

代替案2 構造体ポインタ

どうしても構造体を使いたい場合、unsafe でプロパティを構造体のポインタを型とする。
ポインタだと null になれるので、構造体のサイズが無限になることもない。
ただし、unsafe が必要なのでコードが汚くなる。
unsafe が使えない VB.NET では不可。

unsafe struct Node
{
    public int Value;
    public Node* Next;
}

static class Program
{
    static void Main()
    {
        // 使用例
        var node = new Node() { Value = 1 };
        unsafe {
            node.Next = &node;
            Console.WriteLine(node.Value); // 1
            Console.WriteLine(node.Next->Value); // 1
            Console.WriteLine(node.Next->Next->Value); // 1
        }
    }
}

上記例で構造体ポインタをプロパティで隠せばいいかも、と思うかもしれないが無理。
C# のプロパティは参照渡し不可。

2014年6月16日

クラス(class) vs 構造体(struct)

.NET の型は、クラス(class/Class)構造体(struct/Structure)の2つに大きく分けられる。

クラスは参照型とも呼ばれ、その変数はデータがあるメモリへのアドレス(参照)を保持している。
構造体は値型とも呼ばれ、その変数はデータを直接保持している。
クラスのデータが置かれる場所をヒープ、構造体のデータが置かれる場所をスタックと呼ぶ。

クラスと構造体は両方ともデータとメソッドを持てるので、慣れないうちはどちらを使えばいいか迷うと思う。
とりあえず、使い分けの基準になりそうな違いを挙げる。
  1. 代入(引数への値渡し)時と、ボックス化&ボックス化解除(キャスト)時、構造体はデータをコピーする
    クラスはアドレスのコピーのみ
    ※ 構造体のサイズが大きいほど、代入のコストは大きくなる
  2. 構造体は null にならない
    ※ null とはクラス変数がデータ(参照先)を持ってないことを表すアドレス
  3. インスタンス作成(new)の速度は、クラスより構造体の方が速い
  4. 構造体は GC の対象にならない (スコープから外れた時点で、自動的に消える)
  5. 構造体は他の構造体やクラスから継承できず、継承元にもならない (ただし、インターフェースは実装可能)

補足

  1. についてコードで説明

class ElementC
{
    public ElementC(int value) { Value = value; }
    public int Value;
}
struct ElementS
{
    public ElementS(int value) { Value = value; }
    public int Value;
}
static class Program
{
    static void Main(string[] args)
    {
        /* クラス変数の代入 */
        var ec = new ElementC(1);
        Console.WriteLine(ec.Value);  // 1
        var ec_ = ec; /* クラスの代入はアドレスのコピー */
        ec_.Value = 2; /* 同じデータを指しているので ec_ を変更すると ec も変わる */
        /* 結果 */
        Console.WriteLine(ec_.Value); // 2
        Console.WriteLine(ec.Value);  // 2

        /* 構造体変数の代入 */
        var es = new ElementS(10);
        Console.WriteLine(es.Value);  // 10
        var es_ = es; /* 構造体の代入はデータのコピー */
        es_.Value = 20; /* コピーなので es_ を変更しても es には変わらない */
        /* 結果 */
        Console.WriteLine(es_.Value); // 20
        Console.WriteLine(es.Value);  // 10
    }
}
引数の値渡しも代入と同様。「=」が無いため、意識しにくいかも。
List<T> 使用時の間違い易いパターン。

/* ElementC, ElementS の定義は上と同じ */
static class Program
{
    static void Main(string[] args)
    {
        /* クラス変数の値渡し */
        var ecs = new List<ElementC>();
        var ec = new ElementC(1);
        ecs.Add(ec); /* アドレスのコピー */
        Console.WriteLine(ecs[0].Value); // 1
        ec.Value = 2; /* ec を変更すると ecs[0] も変わる */
        /* 結果 */
        Console.WriteLine(ec.Value);     // 2
        Console.WriteLine(ecs[0].Value); // 2

        /* 構造体変数の値渡し */
        var ess = new List<ElementS>();
        var es = new ElementS(10);
        ess.Add(es); /* データのコピー */
        Console.WriteLine(ess[0].Value); // 10
        es.Value = 20; /* es を変更しても ess[0] は変わらない*/
        /* 結果 */
        Console.WriteLine(es.Value);     // 20
        Console.WriteLine(ess[0].Value); // 10
    }
}

  1. について
例外的な構造体として、Nullable<T> が存在するが、これは本当に null になるわけではない。
Nullable<T> は下記のように定義されてる。

public struct Nullable<T> where T : struct
{
    private bool hasValue;
    internal T value;
    /* 以降、省略 */
}
Nullable<T> が null を代入できたり、null と比較できたりするのは、コンパイラが該当する処理に置き換えているためである。
つまり、シンタックスシュガーになっている。

その他の違い

下記が分かり易くまとめられてる。

おまけ

unsafe を使えば、構造体のアドレスを取得できる。
ちょっと反則っぽいが・・・

/* ElementS の定義は上と同じ */
static class Program
{
    static void Main(string[] args)
    {
        var es = new ElementS(10);
        Console.WriteLine(es.Value);  // 10
        unsafe {
            var esp = &es;   /* es のアドレス取得 */
            esp->Value = 30; /* esp の参照先を変更すると、es も変わる */
            Console.WriteLine(esp->Value); // 30
        }
        Console.WriteLine(es.Value); // 30
    }
}

ちなみに、es を object 変数に代入し、その変数を ElementS でキャストして Value を変更するとかはできない。
コンパイルエラーになる。(コンパイラ エラー CS0445

2014年6月7日

IE9.js(ie7-js) でスタイルシートの解析を部分的に無視

記事タイトルのような機能が無かったため、機能追加したものを作成。
https://bitbucket.org/temp_impl/ie9-js/src

オリジナルとの差分
https://bitbucket.org/temp_impl/ie9-js/commits/124d43c6f6d12327aa0c8e20775ba6a106ea2de0

使用方法

<style> や <link> に class="ie9js-ignore" を付けると、スタイルシート解析の対象外になる。
「class に 対象クラス名が含まれているか」で判定しているので、クラス名が複数あってもOK。

<html>
<head>
<!-- スタイルシート解析から無視される -->
<link type="text/css" rel="stylesheet" href="style.ignore.css" class="ie9js-ignore" />
<style type="text/css" class="ie9js-ignore">
div > div:first-child {
    background-color: cyan;
}
</style>

<!-- スタイルシート解析される -->
<link type="text/css" rel="stylesheet" href="style.css" />
<style type="text/css">
div > div:last-child {
    background-color: yellow;
}
</style>

<!--[if lt IE 9]>
<script src="IE9.min.js"></script>
<![endif]-->
</head>

<body>
  <div>
    <div>first</div>
    <div>last</div>
  </div>
</body>
</html>

使いどころ

対象ブラウザ(IE7/IE8)で元から使えるスタイルシートは解析させる必要がないので無視できる。
解析されて javascript 適用のスタイルシートになると、そのままでは DHTML で使えないなどの問題が起こったりする。

また、ファイルサイズが大きい css を無視できると、当然レスポンスが良くなる。
IE7/IE8 に対応している jquery-ui の css など。

2014年5月31日

IE9.js(ie7-js) を DHTML でも使用する方法

IE9.js(ie7-js) は便利だが、画面表示後に変更された HTML(いわゆる DHTML・動的 HTML)に対してスタイルが適用されないことがある。

理由は単純で、主に擬似要素などブラウザが対応していないスタイルシートを javascript により適用しているためである。
javascript によるスタイル適用は画面ロード時の 1 回だけになり、後から変更された HTML に対しては何もできない。

そこで、スタイルを再適用させるための関数 IE7.recalc() が存在する。
IE9.js + DHTML でスタイルシートが適用されない場合は試してみる価値あり。
主に擬似要素を使ってるスタイルで有効。

使用例


<html>
<head>
<style type="text/css">
div > div:first-child > span {
    background-color: cyan;
}
</style>
<!--[if lt IE 9]>
<script src="http://ie7-js.googlecode.com/svn/version/2.1(beta4)/IE9.js"></script>
<![endif]-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
    var index = 0;
    $('#add_dynamic').click(function(){
        $('#dynamic').append('<div><span>' + (index++) + '</span></div>');
        IE7.recalc();
    });
});
</script>
</head>

<body>
<input type="button" id="add_dynamic" value="add" />
<div id="dynamic"></div>
</body>
</html>
※first-child は IE7・IE8 でも使用可能だが、IE9.js を使用すると javascript 適用のスタイルシートになる。

参考URL

2014年5月30日

IE7 テキストボックスの上下余白(margin)

IE でテキストボックス <input type="text" /> を縦方向に並べる際、テキストボックスに margin:0 を指定しても、IE のバージョンによっては上下に余白ができることがある。
特に IE7 において顕著である。
※横方向に並べる際は問題ない。

具体例

[CSS]

div {
    margin: 0;
    padding: 0;
    width: 120px;
    /* 見易さのためのオプション */
    background-color: #00ffff;
    text-align: center;
}
div input {
    margin: 0;
    width: 50px;
    border: 1px solid #000000;
}
[HTML]
※DOCTYPE を指定しないとバージョン間の差異はさらに増える

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- 中略 -->
<div>
<input type="text" /><input type="text" /><br/>
<input type="text" /><input type="text" />
</div>
[プレビュー]
IE11
IE8~10もほぼ同じ
IE11 textbox
IE7 IE7 textbox

解決手段

見た目どおり上下の余白を取り除くという発想で、<input> に "margin: -1px 0" を指定しただけでは、縦中央に余白が残る。
IE7 textbox minus margin

そこで、<div> に font-size:12px を指定してみると、縦中央の余白も無くなる。
(理由は不明、高さ関係の計算で font-size が必要とか?)
<input> に対してではなく、それを包括する要素に指定するのがポイント
IE7 textbox minus margin and font-size

[IE7 に対応した CSS]

div {
    margin: 0;
    padding: 0;
    width: 120px;
    font-size: 12px;
    /* 見易さのためのオプション */
    background-color: #00ffff;
    text-align: center;
}
div input {
    margin: 0;
    width: 50px;
    border: 1px solid #000000;
}
/* IE7 のみ適用 */
*+html div input {
    margin: -1px 0;
}

/* [おまけ] reset.css で記載する場合
 * ※属性セレクタを使用するため、IE9.js などが必要
 */
*+html input[type=text] {
    margin: -1px 0;
}

備考

<input> に padding:0 を指定すると、IE8 と IE9 でも上に余白ができるが、上記と同様に <div> に font-size を指定すれば無くなる。
クロスブラウザ対応で font-size の指定は意外と重要。

2014年5月5日

再帰的なラムダ式

あまり実用的ではないが、.NET でラムダ式で再帰を行う方法はいくつかある。
ここでは、階乗を再帰の例とする。

ちなみに、普通の再帰メソッド。

public static int Factorial(int x)
{
    return x > 1 ? x * Factorial(x - 1) : 1;
}

1. ラムダ式割り当て変数で再帰


Func<int, int> fac1 = null;
fac1 = x => x > 1 ? x * fac1(x-1) : 1;
ラムダ式内で、自身を割り当てる変数をメソッドとして使用する方法。
たぶん、一番シンプルな方法になる。
ただし、コンパイラの都合上、変数宣言と同時にラムダ式の代入はできない。
※「未割り当てのローカル変数 '~' が使用されました。」エラーが出る。

また、ひねくれた使い方をするとバグの原因になる。

Func<int, int> fac1 = null;
fac1 = x => x > 1 ? x * fac1(x-1) : 1;
var fac1copy = fac1;
Console.WriteLine(fac1(4));     // 24
Console.WriteLine(fac1copy(4)); // 24

fac1 = x => x * x;
Console.WriteLine(fac1(4));     // 16 : 4 * 4
Console.WriteLine(fac1copy(4)); // 36 : 4 * fac1(3) = 4 * (3 * 3)

// fac1copy は最初の fac1 の内容から変わってないが、再帰として fac1 を使っているため、
// fac1 を変更すると影響を受けてしまう。

2. リフレクションで再帰


Func<int, int> fac2 = x =>
    x > 1 ? x * (int)MethodBase.GetCurrentMethod().Invoke(null, new object[]{x-1}) : 1;
リフレクションにより、自身を呼び出す方法。
javascript の arguments.callee (今や非推奨)に近い。
リフレクションなのでパフォーマンスはアレだが、1. のような副作用はない。

3. 不動点コンビネータで再帰

再帰メソッドを引数で渡せばいいかも?という発想から下記を定義する。

Func<Func<int, int>, Func<int, int>> fac3base = (Func<int, int> f) => {
    return (int x) => {
        return x > 1 ? x * f(x - 1) : 1;
    };
};

// 型推論バージョン
// Func<Func<int, int>, Func<int, int>> fac3base = f => x => x > 1 ? x * f(x-1) : 1;

ここで、fac3base の引数は?ということに頭を悩ませると思う。
なぜなら、渡したい中身は fac3base に定義されているから・・・

とりあえず、Func<Func<int, int>, Func<int, int>> から Func<int, int> を取り出す処理、つまり、
Func<
  Func<Func<int, int>, Func<int, int>>,
  Func<int, int>
>
となるメソッド/ラムダが欲しい、ということで不動点コンビネータFixed-point combinator)の出番である。

  • Z コンビネータ
wikipedia どおりの実装。

// 自身を引数に持つデリゲート ※外部に定義する
delegate T SelfApplicable<T>(SelfApplicable<T> self);

Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>> Z =
(Func<Func<int, int>, Func<int, int>> f) => {
    SelfApplicable<Func<int, int>> Z1 = (SelfApplicable<Func<int, int>> x) => {
        return f((int y) => {
            return x(x)(y);
        }
    };
    return Z1(Z1);
};

// 型推論バージョン
// Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>> Z = f => {
//     SelfApplicable<Func<int, int>> Z1 = x => f(y => x(x)(y));
//     return Z1(Z1);
// };

// 使用方法
var fac3z = Z(fac3base);
Console.WriteLine(fac3z(4)); // 24

  • Y コンビネータ
ここの引用。
※C# による実装なので、厳密には Z コンビネータ

// SelfApplicable は Z コンビネータと同じもの
SelfApplicable<Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>>> Y =
(SelfApplicable<Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>>> y) => {
    return (Func<Func<int, int>, Func<int, int>> f) => {
        return (int x) => {
            return f(y(y)(f))(x);
        };
    };
};
Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>> YFix = Y(Y);

// 型推論バージョン
// SelfApplicable<Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>>> Y =
// y => f => x => f(y(y)(f))(x);
// var YFix = Y(Y);

// 使用方法
var fac3y = YFix(fac3base);
Console.WriteLine(fac3y(4)); // 24

なんで動いてるの?という疑問については、wikipedia や引用元サイト参照。
不動点コンビネータとは、要するに fac3base に渡す引数を fac3base から作り出してくれるものである。

おまけ

3. の汎用メソッド

public static class Combinators<TIn, TOut>
{
    static Combinators()
    {
        SelfApplicable<Func<Func<Func<TIn, TOut>, Func<TIn, TOut>>, Func<TIn, TOut>>>
        Y = y => f => x => f(y(y)(f))(x);
        YCombinator = Y(Y);
    }

    public static readonly
    Func<Func<Func<TIn, TOut>, Func<TIn, TOut>>, Func<TIn, TOut>> ZCombinator = f => {
        SelfApplicable<Func<TIn, TOut>> Z1 = x => f(y => x(x)(y));
        return Z1(Z1);
    };

    public static readonly
    Func<Func<Func<TIn, TOut>, Func<TIn, TOut>>, Func<TIn, TOut>> YCombinator;
}
public delegate T SelfApplicable<T>(SelfApplicable<T> self);

参考URL

2014年4月25日

Visual Studio の CSS の警告がうざい場合の対処法

Visual Studio 2010 の場合、真っ当な対処法として、Web Standards Update for Microsoft Visual Studio 2010 SP1 をインストールし、css ファイルを開いた際のツールバーで CSS 3.0 を選択する。
※ただし、設定維持の動作がおかしく、aspx ファイルを開くと CSS 2.1 に戻されたりする

上記を行っても、いくつかの警告は残る。
  • @charset
  • 非対応プロパティ (ime-mode など)
  • 妥当でない CSS ハック

そこで、Visual Studio の設定により、CSS の警告を出さないようにすることができる。
※同時にプロパティの typo などを検出できなくなる点に注意
  1. メニューの [ツール] → [オプション] で、オプション画面を表示
  2. [テキスト エディター] → [CSS] → [その他]
  3. [エラーの検出]チェックを外す
    [CSS][その他]
  4. [OK]を押して設定を保存

2014年4月22日

ul > li > div でテーブルレイアウト

html + css で <table> を使わずにテーブルレイアウトを作る方法をググったところ、<div> のみで構築する方法と、<ul> <li> <div> で構築する方法が多かった。
<div> のみだと class が増えそうなので、<ul> <li> <div> で構築する方法を選択。

しかし、検索結果の情報には、なぜか <li> の下の <div> に「float:left;」を使ったものが多い。
「float:left;」の何が問題かというと、「vertical-align」と共存できない・・・

そこで、下記を使ってみた。
ul → table , li → tr , div → td のように使用可能。

ul.table-layout {
    list-style: none;
    display: table;
    margin: 0;
    padding: 0;
}
ul.table-layout > li {
    display: table-row;
}
ul.table-layout > li > div {
    display: table-cell;
    vertical-align: middle;
}

使用例

[HTML ソース]

<ul class="table-layout">
    <li>
        <div style="padding:5px">
            <div style="border:1px solid #ff00ff">て<br>す<br>と</div>
        </div>
        <div style="padding:5px">
            <div style="border:1px solid #ffff00">レイアウト</div>
        </div>
        <div style="padding:5px">
            <div style="border:1px solid #00ffff">で<br>す</div>
        </div>
    </li>
</ul>

[プレビュー]


  • レイアウト

備考

この方法は "dispaly: table" に対応してないブラウザでは不可。
つまり、IE7 等では諦めて <table> を使うのが無難。

2014年4月20日

IEnumerable.IsNullOrEmpty

string.IsNullOrEmpty の配列版。あると便利。
※ null で例外が欲しい場合は、Enumerable.Any() がある。


// generic 版
public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
{
    if( source == null ) return true;
    using( var e = source.GetEnumerator() ) return !e.MoveNext();
}

// 非 generic 版(普通は不要)
public static bool IsNullOrEmpty(this IEnumerable source)
{
    return source == null || !source.GetEnumerator().MoveNext();
}

ちなみに、string にも使用可能。(IEnumerable<char> なので)

おまけ

パフォーマンスを上げたい場合は、下記メソッドを別途定義する。
ICollection<T> を実装しているクラス(List<T> や配列)は、Count の参照だけで済むため、早くなる。

public static bool IsNullOrEmpty<T>(this ICollection<T> source)
{
    return source == null || source.Count == 0;
}

おまけのおまけ

下記パターンでパフォーマンスを測定してみた。
(A) 引数が IEnumerable<T>
(B) 引数が IEnumerable<T> と ICollection<T> (おまけのパターン)
(C) 引数が IEnumerable<T> で、内部で ICollection<T> にキャスト試行(下記実装)

public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
{
    if( source == null ) return true;
    var c = source as ICollection<T>;
    if( c != null ) return c.Count == 0;
    using( var e = source.GetEnumerator() ) return !e.MoveNext();
}

[測定コード]

static void Main(string[] args)
{
    var testdata = new[]{
        new int[]{},
        new[]{1}
    };

    var loopCount = int.Parse(args[0]);
    var result = false;
    var sw = new Stopwatch();
    sw.Start();
    for( var i=0; i<loopCount; i++ ){
        result = testdata[i % testdata.Length].IsNullOrEmpty();
    }
    sw.Stop();
    Console.WriteLine(sw.Elapsed);
    Console.WriteLine(result);
}

[結果]
loopCount (A) (B) (C)
10000 0.0012318 0.0004056 0.0056082
100000 0.0038177 0.0010864 0.0455488
※コマンドプロンプトで実施(csc /optimize)
※10回の算術平均、単位は[s]

結果は、(B) > (A) > (C) となった。(左の方が高性能)

(C) が遅い原因はキャスト試行処理なので、つまり、パフォーマンス目的でキャスト処理を追加する際は要注意。
(B) のようにキャストをオーバーロードに任せられるパターンは、そうすべき。(メソッドが増えるデメリットはあるけど)

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0
Intel(R) Celeron(R) CPU G530 @ 2.40GHz/DDR3 8GB(4GB x 2)

2014年4月18日

.NET に nullReplace() は不要

下記のような共通メソッドを作ったことはないだろうか。
特に Java 出身者なら・・・

/// <summary>
/// null の場合、replacement に変換する。
/// </summary>
public static T NullReplace<T>(this T source, T replacement)
{
    return source == null ? replacement : source;
}

.NET 2.0 からの ?? 演算子(null 合体演算子)の導入により、このメソッドは無用の長物になる。
(.NET 2.0 以降でないとこのメソッドは作れないが、それはスルーで)
?? 演算子は、null 許容型を便利に扱うため導入されたと思われるが、null 許容型だけでなく、普通の参照型にも使える。

// null 許容型
int? num = null;
int num2 = num ?? 0;
Console.WriteLine(num2); // 0

// null 許容型のみ GetValueOrDefault() でも同じことが可能
// ※メソッドなので、引数が必ず評価される点は注意
int num3 = num.GetValueOrDefault(0);
Console.WriteLine(num3); // 0

// 参照型
string text = null;
string text2 = text ?? "<null>"; // NullReplace() と同じ動作
Console.WriteLine(text2); // <null>

ちなみに VB では、2 引数の If 演算子を使う。
※これが使えるのは Visual Studio 2008 から

Dim text As String = Nothing
Dim text2 As String = If(text, "<null>") ' NullReplace() と同じ動作
Console.WriteLine(text2) ' <null>

というわけで、NullReplace() の導入はやめよう、という話。

2014年4月17日

.NET バージョン毎にプロジェクトを分けてソースを共有する

たまに、複数の .NET バージョンでモノが欲しい時がある。
その際、複数のプロジェクトファイル(.NET バージョン違い)に同じソース群を参照させる。
その手順のメモ。

※元となるプロジェクトファイルは「MyLibrary.csproj」、バージョンは .NET 4.0 とする。
  1. プロジェクトファイル(csproj/vbproj)を複製
    MyLibrary.csproj → MyLibrary_net35.csproj
  2. バージョン変更する方のプロジェクトファイルの /Project/PropertyGroup/TargetFrameworkVersion を変更
    ・MyLibrary_net35.csproj
    
    <Project ToolsVersion="4.0" DefaultTargets="Build"
             xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <!-- 中略 -->
        <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    
  3. 両方のプロジェクトファイルに /Project/PropertyGroup/BaseIntermediateOutputPath + BaseOutputPath を追加し、/Project/PropertyGroup[@Condition]/OutputPath を変更
    (中間ファイルとバイナリの出力先の変更)
    ・MyLibrary.csproj
    
    <Project ToolsVersion="4.0" DefaultTargets="Build"
             xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <BaseIntermediateOutputPath>obj\net40\</BaseIntermediateOutputPath>
        <BaseOutputPath>bin\net40\</BaseOutputPath>
    
    ・MyLibrary_net35.csproj
    
    <Project ToolsVersion="4.0" DefaultTargets="Build"
             xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <BaseIntermediateOutputPath>obj\net35\</BaseIntermediateOutputPath>
        <BaseOutputPath>bin\net35\</BaseOutputPath>
    
    ・両方
    
    <Project ToolsVersion="4.0" DefaultTargets="Build"
             xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <!-- 中略 -->
      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == '{構成設定}' "">
        <!-- 中略 -->
        <!-- 必要に応じて $(Platform) もパスに組み込む(マルチプラットフォーム等) -->
        <OutputPath>$(BaseOutputPath)\$(Configuration)\</OutputPath>
    
  4. Visual Studio で変更したプロジェクトファイルを再読込
  5. Visual Studio で複製したプロジェクトファイルを追加読込
    ソリューションを右クリック → [追加] → [既存のプロジェクト]
  6. .NET のバージョンに合わせたコード修正

備考

バイナリ出力先を分けるのは当然として、中間ファイル出力先を分けておかないと、Visual Studio の挙動がおかしくなることがある。リビルドでないと、ビルド成功しなかったり・・・
※複数プロジェクトで中間ファイル出力先がかぶることは、Visual Studio でビルド失敗する原因の 1 つ

中間ファイル出力先は、IntermediateOutputPath で構成設定毎に指定することも可能。
ただし、中間ファイル出力先を変更しても、「obj\Debug」「obj\Release」などのフォルダは作られる・・・

参考URL

2014年4月14日

Dictionary.TryGetValue のすゝめ

※ Dictionary について熟知している人は読み飛ばし推奨

Dictionary.TryGetValue というメソッドがある。
初見だと「何のためにあるの?」と疑問を抱く人は多い・・・はず。
処理内容が インデクサ(Item プロパティ) とかぶっているため、使う必要性を感じられずに無視してる人もいると思う。

結論から言うと、このメソッドは key の存在を確認してから value を取り出すパターンにおいて、非常に有効である。
  • ContainsKey + インデクサ

var dic = new Dictionary<string, int>();

// ---- key 追加処理 ----

if( dic.ContainsKey("ススメーススメー") ){
    Console.WriteLine(dic["ススメーススメー"]);
}
  • TryGetValue

var dic = new Dictionary<string, int>();

// ---- key 追加処理 ----

int val;
if( dic.TryGetValue("ススメーススメー", out val) ){
    Console.WriteLine(val);
}

上記の差は、TryGetValue 使用時の方が、key ルックアップが 1 回少なくなる点である。
key ルックアップはあまり早い処理ではないため、処理回数は少ないほどいい。

2014/09/26 修正:
Dictionary のソースを見て MSDN を読み間違えてたことに気付きました。
読み取り処理だけならスレッドセーフ、変更処理を含めるとスレッドセーフではない。

パフォーマンス

[測定コード]

static void Main(string[] args)
{
    // データの準備
    var dic = new Dictionary<string, int>();
    var key = "";
    for( var i=0; i<10; i++ ){
        key += "ススメー";
        dic.Add(key, i + 1);
    }
    int loopCount = 10000;
    var temp = new List<int>(loopCount);
    var flag = int.Parse(args[0]);
    var searchKey = args[1];

    // 測定実施
    var sw = new Stopwatch();
    sw.Start();
    switch( flag ){
    case 1:
        // ContainsKey + インデクサ
        for( var i=0; i<loopCount; i++ ){
            if( dic.ContainsKey(searchKey) ) temp.Add(dic[searchKey]);
        }
        break;
    case 2:
        // TryGetValue
        int ret;
        for( var i=0; i<loopCount; i++ ){
            if( dic.TryGetValue(searchKey, out ret) ) temp.Add(ret);
        }
        break;
    }
    sw.Stop();
    Console.WriteLine(sw.Elapsed);
}

[結果]
searchKey ContainsKey
+ インデクサ
TryGetValue
存在するキー ススメーススメー 0.0011107 0.0006471
存在しないキー ススメ 0.0002466 0.0002814
※コマンドプロンプトで実施(csc /optimize)
※10回の算術平均、単位は[s]

存在するキーの場合は、ルックアップが 1 回少ない TryGetValue の圧勝。
存在しないキーの場合は、ContainsKey と TryGetValue の速度差になる。

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0
Intel(R) Celeron(R) CPU G530 @ 2.40GHz/DDR3 8GB(4GB x 2)

参考URL

2014年4月11日

Visual Studio の VB.NET 開発で、最初にやって欲しいこと

最初に 1 回だけ設定すると、幸せになれる。(個人差あり)
  1. Visual Studio を起動する。
  2. メニューの [ツール] → [オプション] で、オプション画面を表示
  3. [プロジェクトおよびソリューション] → [Visual Basic の規定値] を選択
  4. 画像のように設定
    ※デフォルトは Option Strict「Off」
    [Visual Basic の規定値]
  5. [OK]を押して設定を保存

効果

VB.NET プロジェクトを新規作成する際、プロジェクトプロパティのデフォルトが上記となる。
これで、毎回 Option Strict「On」にする必要も、変更し忘れる心配も無し♪♪

※本稿は VS 2010 での話だが、VS 2013 でもデフォルトは Option Strict「Off」のまま・・・たぶん今後も変わらないのだろう

2014年4月10日

C#.NET 複数行の文字列リテラルの改行コード

C#.NET は、@"" 形式だと文字列リテラルを複数行に書ける。
その場合の改行コードについて検索してみたが、見つからなかったので検証。(ググり方が下手なだけかも・・・)

[検証コード]

using System;

static class Program
{
    static void Main()
    {
        var text = @"minami
kotori";
        Console.WriteLine(text.Replace("\r", "[CR]").Replace("\n", "[LF]"));
    }
}

[結果]
ソースの
改行コード
結果
CRLF minami[CR][LF]kotori
CR minami[CR]kotori
LF minami[LF]kotori

複数行の文字列リテラルの改行コードは、ソースの改行コードと同じになる。
Visual Studio 使ってれば問題ないだろうけど、低機能のテキストエディタで改行コードごちゃまぜだったりすると・・・

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

2014年4月9日

VB.NET の Object 型からは拡張メソッド使用不可

Object 型から拡張メソッドを使うことは滅多にないだろうけど、たまたまそのようなコードを書いて、拡張メソッドが使えなくてビビったことがある。
C#.NET にこの問題はなく、VB.NET 固有の問題になる。

  • 拡張メソッド

public static class Extensions
{
    public static int GetHashCodeSafely<T>(this T source)
    {
        return source == null ? 0 : source.GetHashCode();
    }
}
  • C#.NET から利用

var num = 1;

// 問題無し
var hash = num.GetHashCodeSafely();
// 問題無し
hash = ((object)num).GetHashCodeSafely();
  • VB.NET から利用

Dim num = 1

' 問題無し
Dim hash = num.GetHashCodeSafely()
' コンパイル不可「Option Strict On では、遅延バインディングを使用できません。」
hash = DirectCast(num, Object).GetHashCodeSafely()

遅延バインディングとは、VB.NET の機能で「Option Strict Off」時に Object 型に対してその定義にないメソッドやプロパティを呼べること。
.NET 4.0 からの dynamic 型と同じようなことが、VB.NET では Object 型に対して可能。

Option Strict Off

Dim text As String = ""
Dim obj As Object = text
Console.WriteLine(obj.Length)  ' 0 が出力される
Console.WriteLine(obj.Lengths) ' Lengths メンバは存在しないため、実行時エラー

つまり、VB.NET で Object 型から生えてるものは、自身のメンバを除いてすべて遅延バインディングと判断される。
そのため、呼び出し方法がかぶる拡張メソッドは使えない。(初期の VB.NET 開発時に拡張メソッドなんて想定してないだろうし・・・)
「Option Strict On」の時のみ Object 型に対する拡張メソッドを有効にできれば理想だけど、たぶんコンパイラの修正が難しいのだろう。

今後、この問題が改良される可能性は低いだろうから、Object 型に対しては、あきらめて普通の静的メソッドを使うしかない。

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

参考URL

2014年4月8日

VB の Module ≒ C# の static class

Java/C# ユーザーが初めて VB.NET に触れた時、「Module(モジュール)って何?」という疑問にぶち当たる確率は高いと思う。
簡単に言ってしまえば、静的メンバのみのクラス(C# の static class)で、呼び出し側でクラス名(モジュール名)を省略できる特徴がある。
文字だと分かりにくいのでコードで

Public Module ModuleSample
    Sub New()
        ' コンストラクタも定義できる(静的コンストラクタに該当)
        Console.WriteLine("ModuleSample.New")
    End Sub

    Public Sub TestCall()
        ' Shared が無いけど、静的メソッド(共有メソッド)
        Console.WriteLine("ModuleSample.TestCall")
    End Sub
End Module

Module EntryPoint
    ' モジュール/クラスの Main メソッドはプログラムのエントリポイント
    Sub Main()
        ' 呼び出し時
        TestCall()              'モジュール名を省略できる
        ModuleSample.TestCall() '省略しないことも可能
    End Sub
End Module

'[結果]
' ModuleSample.New
' ModuleSample.TestCall
' ModuleSample.TestCall

C# からは、static class として扱える。

static class EntryPoint
{
    static void Main()
    {
        ModuleSample.TestCall();
    }
}

//[結果]
// ModuleSample.New
// ModuleSample.TestCall

Module の実態は static class とほぼ同じなのだが、C# の static class を VB から呼ぶ場合は Module のようにクラス名を省略できない。その違いは、StandardModuleAttribute である。
この属性を C# の static class に付けてやると、VB から呼ぶ場合にクラス名を省略できる。
ただ、MSDN の説明に「独自に作成したコードから直接使用するためのものではありません。」「これは、コードから直接呼び出すためのものではありません。」とあり、ユーザーコードからは使って欲しくない模様・・・

using System;
using Microsoft.VisualBasic.CompilerServices;

[StandardModule]
public static class CShapModuleSample
{
    public static void TestCall()
    {
        Console.WriteLine("CShapModuleSample.TestCall");
    }
}

MSIL の比較

VB - Module .class public auto ansi sealed
C# - static class .class public abstract auto ansi sealed beforefieldinit
static class には、abstract/beforefieldinit が付いている。
static class の場合、beforefieldinit は静的コンストラクタがあると付かないが、Module の場合、コンストラクタの有無に関わらず付かない。
つまり、Module のフィールドは必ず最初のアクセス時に初期化される。(アクセスがなければ初期化されない)

拡張メソッド

VB の拡張メソッドは Module でしか作れない。

Imports System.Runtime.CompilerServices

Public Module Extensions
    <Extension()>
    Public Function IsNotNull(Of T)(ByVal source As T) As Boolean
        Return source IsNot Nothing
    End Function
End Module

' 呼び出し方法は 3 通り可能・・・
Dim val = ""
val.IsNotNull()
IsNotNull(val)
Extensions.IsNotNull(val)

まとめ

Module は多用しない方がいい。
クラス名(モジュール名)を省略することは、名前の衝突確率を上げることになり、あまりよくない。
プログラムのエントリポイント、拡張メソッド以外での利用は避けた方がいい。

どうしても使いたい場合は、アクセス修飾子を Friend にする、専用の名前空間にする、などの条件を設けるべき。
余談だが、拡張メソッドも名前の衝突確率が高いので、専用の名前空間を推奨。

VB は Module があるためか、静的クラスが無いので、代わりに下記のような疑似静的クラスを使ったりする。

' NotInheritable で継承禁止
' Private コンストラクタでインスタンス生成を禁止
Public NotInheritable Class StaticClass
    Private Sub New()
    End Sub

    ' ---- 以降、Shared メンバを定義 ----
End Class

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

2014年4月7日

C#.NET でインデクサの名前変更

C#.NET のインデクサのデフォルトの名前は Item である。
機会は少ないが、下記のように Item を別のメンバの名前にする場合、そのままではコンパイルが通らない。

public class IndexerName
{
    // コンパイルエラーになる
    public string this[int index] { get { return index.ToString(); } }

    public string Item;
}

そこで、IndexerNameAttribute で、インデクサの名前を変更することができる。

using System.Runtime.CompilerServices;

public class IndexerName
{
    // インデクサの名前が"Indexer"になり、コンパイルエラーにならない。
    // ※変数名の禁則文字は使えない。(エラーになる)
    // ※VB からなら instance(0) の他に、instance.Indexer(0) でもアクセス可能
    [IndexerName("Indexer")]
    public string this[int index] { get { return index.ToString(); } }

    public string Item;
}

参考URL

2014年4月4日

プロパティの比較

項目 C#.NET VB.NET
静的プロパティ
読込専用プロパティ
書込専用プロパティ
get, set 毎のアクセス修飾子
引数付きプロパティ
インデクサ
自動プロパティ
自動プロパティの
get, set 毎のアクセス修飾子
自動プロパティの初期化
自動プロパティの
バッキングフィールド
<PropertyName>k__BackingField _PropertyName
※VB.NET の get, set 毎のアクセス修飾子は、Visual Studio 2008/2010 で可能、2003 では不可であることを確認。
MSDN を信じると Visual Studio 2005 から可能。

  • C#.NET

using System;
using System.Reflection;

class CSharpProperties
{
    // 静的プロパティ
    public static int SValue
    {
        get { return -1; }
        set { Console.WriteLine(value); }
    }

    // 読取専用プロパティ
    public int RValue { get { return 0; } }

    // 書込専用プロパティ
    public int WValue { set { Console.WriteLine(value); } }

    // get, set 毎のアクセス修飾子
    public int RWValue
    {
        get { return 0; } // 省略時はプロパティと同じ = public
        private set { Console.WriteLine(value); }
    }

    // 引数付きプロパティ -> 不可
    // public string ParamValue[int index] { get { return index.ToString(); } set { } }

    // インデクサ
    public string this[int index]
    {
        get { return index.ToString(); }
        set { Console.WriteLine("[{0}] = {1}", index, value); }
    }

    // 自動プロパティ
    public int AutoValue { get; set; }

    // 自動プロパティの get, set 毎のアクセス修飾子
    public int RAutoValue { get; private set; } // 外部から読取専用
    public int WAutoValue { private get; set; } // 外部から書込専用

    // 自動プロパティの初期化 -> 不可
    // public int IAutoValue { get; set; } = 1;

    // 自動プロパティのバッキングフィールドへのアクセス -> リフレクションで可能
    public void AccessToAutoValue()
    {
        var fi = this.GetType().GetField("<AutoValue>k__BackingField",
                                         BindingFlags.Instance | BindingFlags.NonPublic);
        Console.WriteLine(fi.GetValue(this));
    }
}

  • VB.NET

Imports System

Class VbProperties
    ' 静的プロパティ
    Public Shared Property SValue As Integer
        Get
            Return -1
        End Get
        Set(value As Integer)
            Console.WriteLine(value)
        End Set
    End Property

    ' 読取専用プロパティ
    Public ReadOnly Property RValue As Integer
        Get
            Return 0
        End Get
    End Property

    ' 書込専用プロパティ
    Public WriteOnly Property WValue As Integer
        Set(value As Integer)
            Console.WriteLine(value)
        End Set
    End Property

    ' get, set 毎のアクセス修飾子
    Public Property RWValue As Integer
        Get
            Return 0
        End Get
        Private Set(value As Integer)
            Console.WriteLine(value)
        End Set
    End Property

   ' 引数付きプロパティ
    Public Property ParamValue(ByVal index As Integer) As String
        Get
            Return index.ToString()
        End Get
        Set(value As String)
            Console.WriteLine("[{0}] = {1}", index, value)
        End Set
    End Property

    ' インデクサ
    Default Public Property Item(ByVal index As Integer) As String
        Get
            Return index.ToString()
        End Get
        Set(value As String)
            Console.WriteLine("[{0}] = {1}", index, value)
        End Set
    End Property

    ' 自動プロパティ
    Public Property AutoValue As Integer

    ' 自動プロパティの get, set 毎のアクセス修飾子 -> 不可
    ' Public Property RAutoValue As Integer
    '     Get
    '     Private Set
    ' End Property

    ' 自動プロパティの初期化
    Public Property IAutoValue As Integer = 1

    ' 自動プロパティのバッキングフィールドへのアクセス -> 直に可能
    Public Sub AccessToAutoValue()
        Console.WriteLine(_AutoValue)
    End Sub
End Class

まとめ(感想のようなもの)

get, set 毎のアクセス修飾子を設定できる C# のプロパティは、とても便利。
しかもインデクサ、自動プロパティでも可能!!
C# は、自動プロパティで get, set 毎のアクセス修飾子を設定できる点が便利。
ただ、インデクサ以外の引数付きプロパティは使えない・・・残念。

対する VB の利点は、引数付きプロパティが使えること、自動プロパティが初期化できること。
欠点としては、バッキングフィールドに簡単にアクセスできること。
バッキングフィールドと同じ名前のメンバが定義できず、地味に不便。

おまけ

C# から VB の引数付きプロパティへのアクセスは下記で可能。
・・・ダサい。

var instance = new VbProperties();
// getter
var temp = instance.get_ParamValue(1);
// setter
instance.set_ParamValue(1, "a");

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

参考URL

2014年4月2日

VB.NET に ByOut は無いが OutAttribute はある

VB.NET に C#.NET の out に相当するものは、残念ながら存在しない。

VB.NET だけなら ByRef を使用していればいいのだが(使用するしかない)、VB.NET で作成した ByRef 引数を持つメソッドを C#.NET から呼ぶ場合、ref / out が区別できないと不便である。(ByRef のみでは ref に相当する)
これを解決する方法として、OutAttribute がある。


' C# から呼ぶ場合、引数"ret"は ref として扱える。
Public Shared Function AddRef(ByVal x As String, ByVal y As String,
                              ByRef ret As Integer) As Boolean
    Dim xi, yi As Integer
    If Integer.TryParse(x, xi) AndAlso Integer.TryParse(y, yi) Then
        ret = xi + yi
        Return True
    End If
    Return False
End Function

' C# から呼ぶ場合、引数"ret"は out として扱える。
Public Shared Function AddOut(ByVal x As String, ByVal y As String,
                              <Out()> ByRef ret As Integer) As Boolean
    Dim xi, yi As Integer
    If Integer.TryParse(x, xi) AndAlso Integer.TryParse(y, yi) Then
        ret = xi + yi
        Return True
    End If
    Return False
End Function

ただし、この属性は、C#.NET から呼ぶ場合に out として扱えるだけで、VB.NET に out 機能を提供するわけではないことに注意。
あくまで VB.NET は、ref = ByRef のみ。

2014年3月28日

C# と VB のちゃんぽん

C#.NET と VB.NET のプロジェクト(csproj/vbproj)を 1 つソリューション(sln)に含めることができる。
同じ IL を生成するので当然といえば当然なんだが、IDE のデメリットが多いため、オススメしない。

Visual Studio 2010 の場合、言語が異なるプロジェクトに対して、下記の不便がある。
  • 参照先をビルドしてないと、インテリセンスが効かない
  • 「定義へ移動」で、ソース本体に移動せず、定義が表示される
  • 「すべての参照を検索」で、検索されない

要は、プロジェクト参照なのに、実質、DLL 参照状態になる。
特別な理由がない限り、ちゃんぽんは避けるべき・・・

※デバッグ実行については、ちゃんぽんでも追ってくれる

2014年3月27日

char.IsDigit() / 正規表現"\d" の注意事項

char.IsDigit()正規表現の"\d" は、半角数字にマッチする。
が、それだけでなく、UNICODEカテゴリ"Nd"(Number, Decimal Digit) にもマッチする。

上記を意識して使わないと、高確率でバグを仕込んでしまう。
例えば、日本語システムだと全角数字("0" ~ "9")の入力が想定できるため、半角数字と区別する必要がある場合に、これらを使用するとバグる。
他に、これらを使ってチェックし、int.Parse() に投げるなども NG。
※普通は、int.TryParse() 推奨

UNICODEカテゴリ"Nd"にマッチすることの検証

[検証コード]

using System;
using System.Globalization;
using System.Text.RegularExpressions;

static class Program
{
    static void Main()
    {
        foreach( var point in DigitZeroPoints ){
            for( var c = point; c < point + 10; c++ ){
                var char_isdigit = char.IsDigit(c);
                var regex_isdigit = Regex.IsMatch(c.ToString(), @"\d");
                if( !char_isdigit || !regex_isdigit ){
                    // どちらかのマッチ失敗時のみコンソール出力
                    Console.WriteLine(
                        "{0:X4} - char={1} regex={2} category={3}",
                        (int)c, char_isdigit, regex_isdigit,
                        CharUnicodeInfo.GetUnicodeCategory(c));
                }
            }
        }
    }

    // UNICODEカテゴリ"Nd"の ZERO のコードポイント一覧
    // ※FileFormat.Info から 2014/03/26 に取得
    // ※4Byte 文字は char で表現できないため、除外
    private static readonly char[] DigitZeroPoints = new[] {
        '\u0030',  // DIGIT ZERO
        '\u0660',  // ARABIC-INDIC DIGIT ZERO
        '\u06F0',  // EXTENDED ARABIC-INDIC DIGIT ZERO
        '\u07C0',  // NKO DIGIT ZERO
        '\u0966',  // DEVANAGARI DIGIT ZERO
        '\u09E6',  // BENGALI DIGIT ZERO
        '\u0A66',  // GURMUKHI DIGIT ZERO
        '\u0AE6',  // GUJARATI DIGIT ZERO
        '\u0B66',  // ORIYA DIGIT ZERO
        '\u0BE6',  // TAMIL DIGIT ZERO
        '\u0C66',  // TELUGU DIGIT ZERO
        '\u0CE6',  // KANNADA DIGIT ZERO
        '\u0D66',  // MALAYALAM DIGIT ZERO
        '\u0E50',  // THAI DIGIT ZERO
        '\u0ED0',  // LAO DIGIT ZERO
        '\u0F20',  // TIBETAN DIGIT ZERO
        '\u1040',  // MYANMAR DIGIT ZERO
        '\u1090',  // MYANMAR SHAN DIGIT ZERO
        '\u17E0',  // KHMER DIGIT ZERO
        '\u1810',  // MONGOLIAN DIGIT ZERO
        '\u1946',  // LIMBU DIGIT ZERO
        '\u19D0',  // NEW TAI LUE DIGIT ZERO
        '\u1A80',  // TAI THAM HORA DIGIT ZERO
        '\u1A90',  // TAI THAM THAM DIGIT ZERO
        '\u1B50',  // BALINESE DIGIT ZERO
        '\u1BB0',  // SUNDANESE DIGIT ZERO
        '\u1C40',  // LEPCHA DIGIT ZERO
        '\u1C50',  // OL CHIKI DIGIT ZERO
        '\uA620',  // VAI DIGIT ZERO
        '\uA8D0',  // SAURASHTRA DIGIT ZERO
        '\uA900',  // KAYAH LI DIGIT ZERO
        '\uA9D0',  // JAVANESE DIGIT ZERO
        '\uAA50',  // CHAM DIGIT ZERO
        '\uABF0',  // MEETEI MAYEK DIGIT ZERO
        '\uFF10',  // FULLWIDTH DIGIT ZERO
    };
}

[結果]
1A80 - char=False regex=False category=OtherNotAssigned
:
1A89 - char=False regex=False category=OtherNotAssigned
1A90 - char=False regex=False category=OtherNotAssigned
:
1A99 - char=False regex=False category=OtherNotAssigned
A9D0 - char=False regex=False category=OtherNotAssigned
:
A9D9 - char=False regex=False category=OtherNotAssigned
ABF0 - char=False regex=False category=OtherNotAssigned
:
ABF9 - char=False regex=False category=OtherNotAssigned

TAI THAM HORA DIGIT (1A80~9)、TAI THAM THAM DIGIT (1A90~9)、JAVANESE DIGIT (A9D0~9)、MEETEI MAYEK DIGIT (ABF0~9) が数字文字として判定されず、UNICODEカテゴリが"Cn"(Other, Not Assigned) となった。

原因は、判定されなかった文字の UNICODE バージョンは 5.2.0 で、Windows7/.NET 4.0 の UNICODE バージョンは 5.1 だからである。(参考:.NET の UNICODE バージョン
環境がないので検証できないが、Windows 8/.NET 4.5 なら数字文字として判定されるはず。

おまけ

半角数字のみを判定したい場合は、下記を使っている。

public static bool IsAsciiDigit(this char c)
{
    return '0' <= c && c <= '9';
}

正規表現の場合

// \d を使わず素直に書く
var regex_isdigit = Regex.IsMatch(c.ToString(), "[0-9]");

// または

// ECMAScript 準拠の正規表現ならば、半角数字のみにマッチする
var regex_isdigit = Regex.IsMatch(c.ToString(), @"\d", RegexOptions.ECMAScript);

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0

2014年3月26日

.NET の UNICODE バージョン

.NET OS UNICODE ソース
4.5 Windows 8 Unicode 6.0 standard .NET 4.5 - String
Windows 7
Windows Vista
Unicode 5.0 standard
4.0 すべて Unicode 5.1 standard .NET 4.0 - String
3.5 すべて Unicode 5.0 standard .NET 3.5 - UnicodeCategory
3.0 すべて Unicode 3.1 standard .NET 3.0 - UnicodeCategory
2.0 すべて Unicode 3.1 standard .NET 2.0 - UnicodeCategory
1.1 すべて Unicode 3.1 standard .NET 1.1 - UnicodeCategory
※ソースの MSDN を元に作成。
※MSDN が間違っている場合もあるので注意

UNICODE のバージョン違いで何が変わるかというと、MSDN にもある通り、ソート、ケーシング(大文字・小文字)、正規化(Normalize)などに差が出てくる。

そして、CharUnicodeInfo に大きく影響する。
.NET Framework 内でこれを使っている処理も多く(上に挙げた処理も使っているはず)、.NET のバージョンを変更する際は、意識する必要がある。

参考URL

2014年3月20日

インスタンスのキャッシュ

instance クラス(インスタンスを生成して使うクラス)でも、static なものとして欲しい場合がある。
例えば、IComparer<T>IEqualityComparer<T> を実装したクラスのインスタンスは、基本的に 1 個あれば十分である。

このようなクラスについては、自身のインスタンスを保持する static プロパティ(またはメンバ)を公開する方法がある。

public class OreOreComparer : IComparer<OreClass>
{
    public static Default { get { return _default; } }
    private static readonly _default = new OreOreComparer();

    public int Compare(OreClass x, OreClass y) { /* 実装 */ }
}

// または

public class OreOreComparer : IComparer<OreClass>
{
    public static readonly Default = new OreOreComparer();

    public int Compare(OreClass x, OreClass y) { /* 実装 */ }
}

しかし、クラス毎に変数を用意するのは面倒。かつ、重複コードなのであまり美しくない。
そこで、generic type caching を使う方法を考えてみた。
呼び出し側が長くなるデメリットがあるが、クラス毎に変数を用意する必要がなくなってスッキリ?する。

// 値型をキャッシュしたくない場合は class 制約もあり
public static class InstanceCache<T> where T : new()
{
    public static readonly T Default = new T();
}

// 使い方 : OreClass のインスタンス OreA と OreB を比較する
var comparer = InstanceCache<OreOreComparer>.Default;
var result = comparer.Compare(OreA, OreB);

2014年3月19日

型(Type)をキーにした場合の高速なキャッシュ

キャッシュを扱う場合、通常は、Dictionary<TKey, TValue>ConcurrentDictionary<TKey, TValue> などを選択する。

generic 型(Type) を key にする場合のみ、上記クラスを使用するより高速な方法が存在する。
"generic type caching" と呼ばれる方法である。

2015/03/13 追記:
C# 6.0 からは nameof 演算子があるので、EXAMPLE の GetName メソッドは使わないこと。

EXAMPLE

下記メソッドを、キャッシュを使って高速化する場合を考える。

// 最初のプロパティ名を取得する。
// 例のように匿名型と組み合わせることで変数名を文字列として取得可能。
// int value = 0;
// new{value}.GetName() -> "value"
//
public static string GetName<T>(this T source) where T : class
{
    var properties = typeof(T).GetProperties(); //ここが遅い
    return properties[0].Name;
}

  • ConcurrentDictionary の例 ※パフォーマンス測定の結果、キャッシュ無しよりも遅い

public static string GetName<T>(this T source) where T : class
{
    return getNameCache.GetOrAdd(typeof(T), t => t.GetProperties()[0].Name);
}
private static readonly ConcurrentDictionary<Type,string> getNameCache =
    new ConcurrentDictionary<Type,string>();

  • generic type caching の例

public static string GetName<T>(this T source) where T : class
{
    return GetNameCache<T>.Name;
}
private static class GetNameCache<T>
{
    static GetNameCache()
    {
        var properties = typeof(T).GetProperties();
        Name = properties[0].Name;
    }
    public static readonly string Name;
}
generic type caching とは、要は、generic & static なクラスをキャッシュとして使うことである。
型引数が key、static メンバが value に相当する。
静的コンストラクタは、クラスの呼び出し時に1回だけ実行され、スレッドセーフである。
そして、型引数はコンパイル時に解決されるため、Dictionary などの動的な key ルックアップと比べて、圧倒的に早い。

パフォーマンス

[測定コード]

byte ba=0, bb=0;
int ia=0, ib=0;
float fa=0f, fb=0f;
char ca='\0', cb='\0';
string sa=null, sb=null;
var temp = new List<string>(loopCount);

for( var i=0; i<loopCount; i++ ){
    switch( i % 10 ){
    case 0: temp.Add(new{ba}.GetName()); break;
    case 1: temp.Add(new{bb}.GetName()); break;
    case 2: temp.Add(new{ia}.GetName()); break;
    case 3: temp.Add(new{ib}.GetName()); break;
    case 4: temp.Add(new{fa}.GetName()); break;
    case 5: temp.Add(new{fb}.GetName()); break;
    case 6: temp.Add(new{ca}.GetName()); break;
    case 7: temp.Add(new{cb}.GetName()); break;
    case 8: temp.Add(new{sa}.GetName()); break;
    case 9: temp.Add(new{sb}.GetName()); break;
    }
}

[結果]
loopCount キャッシュ無し ConcurrentDictionary generic type caching
10000 0.0054555 0.0190749 0.0039957
100000 0.0270494 0.1461569 0.0086326
1000000 0.2428146 1.4022937 0.0465277
※コマンドプロンプトで実施(csc /optimize)
※10回の算術平均、単位は[s]

結果としては、generic type caching > キャッシュ無し > ConcurrentDictionary となった。(左の方が高性能)
ConcurrentDictionary のルックアップが想定していたよりも遅く、キャッシュ無しの方が早い結果に…。
※一応、Dictionary でも試してみたが ConcurrentDictionary より 2 倍早くなっただけで、キャッシュ無しより遅かった。

キャッシュ無しと比べて generic type caching は、1 万回でも 1.5 [ms] 程しか差は出なかった。
が、共通メソッドとしてできる限りパフォーマンスを良くしたい場合は、導入の価値ありだと思われる。
また、キャッシュの特性として、value の生成処理のコストがより高い場合は、より大きな効果が見込まれる。

まとめ

generic type caching の導入可能条件は下記になる。
  • key が generic 型 (コンパイル時に決定できる Type 型)
  • 削除不要なキャッシュ (一度作成したキャッシュは削除できない)

使いどころとしては、EXAMPLE のようにリフレクション使用時のパフォーマンス向上がメインとなる。
※Type.Get~ や Enum.Get~ など

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0
Intel(R) Celeron(R) CPU G530 @ 2.40GHz/DDR3 8GB(4GB x 2)

参考URL