http://blogs.wankuma.com/shannon/archive/2007/01/04/54639.aspxの続き。
最初は、こんなクラスを作って対処しようと考えた。
public class OptimizedBrush : Brush
{
private readonly Color _color = Color.Empty;
private readonly Brush _brush = null;
public OptimizedBrush(Color color)
{
_color = color;
if (color.IsSystemColor)
{
_brush = SystemBrushes.FromSystemColor(color);
}
else
{
_brush = new SolidBrush(color);
}
}
public OptimizedBrush(SolidBrush brush)
{
_color = brush.Color;
_brush = (Brush)brush.Clone();
}
public override object Clone()
{
return new OptimizedBrush(_color);
}
public void Dispose()
{
if (!_color.IsSystemColor)
{
_brush.Dispose();
}
}
~OptimizedBrush()
{
Dispose();
}
}
が、またしても撃沈。
おかしいとは思っていたが、この派生クラスのインスタンスをGraphicsのメソッドに渡したとして、そこから色を取得する方法がない。
Brushクラスの説明には、派生クラスの要件として「Cloneを実装せよ」としか書かれていない。
Cloneはしっかり実装しているんだからこれで間違えていないはずだ。ドキュメントが正しければ。
気になったので、ReflectorでSystem.Drawing.BrushおよびSolidBrushの実装を見てみた。
まず、Disposeで落ちた原因だが、やはり予想通り。
SolidBrushはプライベート変数immutableを持っており、これがtrueだとDisposeで例外が飛ぶ。
SystemBrushesから取得したブラシはimmutableがtrueになっていた。
次に、SolidBrushから色を取り出す方法について。
これは.NET 1.1と2.0で微妙に実装が違う。
SolidBrushはコンストラクタ内部で引数のColorからGDIネイティブブラシを作り、これをBrush.SetNativeBrushメソッドに渡している。
1.1ではBrush内部でネイティブブラシハンドルをinternal変数に保持しておき、Graphicsはこれを参照する。
2.0ではより行儀正しく、ネイティブブラシハンドルはprivate変数として保持され、readonlyプロパティを通じてGraphicsに渡される。
で、ここからが問題なのだが…。
Brush.SetNativeBrushは2.0ではprotectedメソッドとして公開されたが、1.1ではinternal。つまり派生クラスから呼び出せない。
よって、.NET 1.1でBrush派生クラスを作る方法は存在しない。
確かに、.NET 1.1のBrushクラスの説明には、派生時の要件とかは書いてないのだけれど…派生するなともどこにも書いてないしなぁ。
派生できないならsealedにしとけよ….NET Framework内ならsealedクラスから派生するようなインチキを他ではいっぱいやってるんだからさぁ。
2.0では、SetNativeBrushが公開されたので、Brush派生クラスは作れるようになった。
が、今回の目的は既存のBrushのラッパーを作ることだ。そして、既存のBrushからネイティブブラシハンドルを取得する方法はない。
よって、派生クラスは作れるが今回の目的には合致しない。
結局、以下のようなクラスを作ることで目的は達した。
public class OptimizedBrushWrapper : IDisposable
{
private readonly Color _color = Color.Empty;
private readonly Brush _brush = null;
public OptimizedBrushWrapper(Color color)
{
_color = color;
if (color.IsSystemColor)
{
_brush = SystemBrushes.FromSystemColor(color);
}
else
{
_brush = new SolidBrush(color);
}
}
public OptimizedBrushWrapper(SolidBrush brush)
{
_color = brush.Color;
_brush = (Brush)brush.Clone();
}
public Brush Brush
{
get
{
return _brush;
}
}
public void Dispose()
{
if (!_color.IsSystemColor)
{
_brush.Dispose();
}
}
~OptimizedBrushWrapper()
{
Dispose();
}
}
OptimizedBrushWrapper.Brush.Dispose();とかされると依然として落ちる可能性があるし、IDisposableなクラスのラッパーを作るのは気が進まないのだが、それは仕方あるまい。
そもそもが、IDisposableを実装するくせにDisposeできないようなクラスを作る奴が悪いのだ。
これでまた一歩、IDisposableが使い物にならなくなったというわけだな。