MarshalByRefObjectから継承する
小ネタ。
通常、MarshalByRefObjectから継承することはあまり無いとは思いますが、RealProxyを使ったAOPモドキなんかで、ひょっとしたら使うことがあるかも知れません。その場合のデメリットを見てみましょう。
using System; using System.Diagnostics; namespace MarshalByRefSample { /// <summary> /// MarshalByRefObjectから継承 /// </summary> class Foo : MarshalByRefObject { public int Counter; } /// <summary> /// Objectから継承 /// </summary> class Bar { public int Counter; } class Program { static void Main(string[] args) { const int max = 10000000; var foo = new Foo(); var sw = Stopwatch.StartNew(); for (var i = 0; i < max; ++i) foo.Counter++; sw.Stop(); Console.WriteLine("{0}:Counter={1}, Time={2}", foo.GetType(), foo.Counter, sw.ElapsedMilliseconds); var bar = new Bar(); sw = Stopwatch.StartNew(); for (var i = 0; i < max; ++i) bar.Counter++; sw.Stop(); Console.WriteLine("{0}:Counter={1}, Time={2}", bar.GetType(), bar.Counter, sw.ElapsedMilliseconds); } } } /* 結果 MarshalByRefSample.Foo:Counter=10000000, Time=901 MarshalByRefSample.Bar:Counter=10000000, Time=31 */
フィールドへのアクセスは自動的にプロキシ経由(+リフレクション)になるため、大きなオーバーヘッドがかかります。あと記憶では、インライン展開の最適化の対象外になったはず(プロキシ経由なので当たり前と言えば当たり前)。
フィールドの初期化とコンストラクタ
小ネタです。
namespace AssemblySize { class Program { // フィールドの初期化が沢山 private int _0 = 0; private int _1 = 1; private int _2 = 2; private int _3 = 3; private int _4 = 4; private int _5 = 5; private int _6 = 6; private int _7 = 7; private int _8 = 8; private int _9 = 9; // 最終的に1つのコンストラクタが呼ばれる public Program() : this(0) {} public Program(int a) : this(0,0) {} public Program(int a, int b) : this(0,0,0) {} public Program(int a, int b, int c) : this(0,0,0,0) {} public Program(int a, int b, int c, int d) : this(0,0,0,0,0) {} public Program(int a, int b, int c, int d, int e) {} static void Main(string[] args) { var program = new Program(); } } } /* ビルド後のアセンブリのサイズ:5120byte */
上記のコードをビルドするとアセンブリは5120byteでした。一方、
namespace AssemblySize { class Program { // フィールドの初期化が沢山 private int _0 = 0; private int _1 = 1; private int _2 = 2; private int _3 = 3; private int _4 = 4; private int _5 = 5; private int _6 = 6; private int _7 = 7; private int _8 = 8; private int _9 = 9; // コンストラクタはそれぞれ独立 public Program() {} public Program(int a) {} public Program(int a, int b) {} public Program(int a, int b, int c) {} public Program(int a, int b, int c, int d) {} public Program(int a, int b, int c, int d, int e) {} static void Main(string[] args) { var program = new Program(); } } } /* ビルド後のアセンブリのサイズ:5632byte */
こちらは、5632byteと若干大きくなっています。
両方ともフィールドに対し値を直接初期化していますが、このコードは当然のごとく、コンストラクタに展開されます。最初の例では、コンストラクタをthisでつないで最終的に1つのコンストラクタが呼ばれることになるので、ここにコードが展開されますが、2つ目の例はコンストラクタが独立しているため、それぞれにコードが展開され、サイズが膨らんでいます。
良く考えてみれば当たり前の話ですが、ソースコードだけ見ていると、ついつい忘れがちになりますよね。(^^;
カスタム属性なしのMEF
CodePlexにあるMEF v2ではカスタム属性ではなくコードでImport/Exportが可能になっています。「おいおい、依存関係の解決をコードでやらずに設定ファイルに出して、設定ファイルが複雑になるのでカスタム属性で指定できるようになったのに、また、コードに戻るのかいな?」と疑問に思われるかも知れません。確かにその通り。ただ、カスタム属性は便利ですが、属性を付けるにはソースコードの変更が必要で、バイナリレベルでの再利用はし難いという制約もあり、その場合の逃げ手になったり、依存関係の指定方法の自作をする場合に便利かも知れません。
で、やり方。
まずは、いつものインタフェース。
namespace MefSample { public interface IGreeting { void Say(string name); } }
次に、実装クラスを2つばかり。
using System; using System.ComponentModel.Composition; namespace MefSample { // [Export(typeof(IGreeting))] public class Hello : IGreeting { public void Say(string name) { Console.WriteLine(@"Hello, {0}", name); } } }
using System; using System.ComponentModel.Composition; namespace MefSample { // [Export(typeof(IGreeting))] public class Goodby : IGreeting { public void Say(string name) { Console.WriteLine(@"Goodby, {0}", name); } } }
カスタム属性がコメントアウトされていることに注意してください。
では、依存関係の解決をします。
using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.Registration; namespace MefSample { // [Export] public class Program { // [ImportMany] public IEnumerable<IGreeting> Greetings { get; set; } public void Say() { foreach (var greeting in Greetings) greeting.Say("mei"); } static void Main(string[] args) { var registration = new RegistrationBuilder(); // Helloクラスを登録してIGreetingインタフェースをエクスポート registration.OfType<Hello>().Export<IGreeting>(); // Goodbyクラスを登録してIGreetingインタフェースをエクスポート registration.OfType<Goodby>().Export<IGreeting>(); #if false // IGreetingインタフェースを実装しているクラスを登録してIGreetingインタフェースをエクスポート // こうすると、Hello, Goodby両方を一度に登録できる(IGreetingを実装しているので) registration.Implements<IGreeting>().Export<IGreeting>(); #endif // Programを登録し、型がIEnumerable<IGreeting>のプロパティにインポートして、Programをエクスポート // ImportManyの場合は、第二引数のdelegateでAsMany()とする必要がある registration.OfType<Program>() .ImportProperty(property => property.PropertyType == typeof (IEnumerable<IGreeting>), (p, b) => b.AsMany()) .Export<Program>(); var catalog = new AssemblyCatalog(typeof (Program).Assembly, registration); var container = new CompositionContainer(catalog); // エクスポートされているProgramを取得 var program = container.GetExportedValue<Program>(); program.Say(); } } } /* 結果 Hello, mei Goodby mei */
と、こんな感じです。もちろん、カスタム属性と組み合わせることもできるので、外部DLLはコードでインポート/エクスポートして自前はカスタム属性を使うというのもアリです。
小ネタ
ソースレビューをしていてAnyで充分なところでCountを使っているケースを見かけたので。STLのempty/sizeの使い分けと同様ですね。
using System; using System.Diagnostics; using System.Linq; class Program { static void Main() { var q = Enumerable.Range(0, 1000); // 要素数をチェック var sw = Stopwatch.StartNew(); for (var i = 0; i < 100000; ++i) { bool exist = q.Count() > 0; } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); // 存在をチェック sw = Stopwatch.StartNew(); for (var i = 0; i < 100000; ++i) { bool exist = q.Any(); } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } } /* 結果 876 7 */
久々の小ネタでした。
C#の型推論は怠けすぎ
2010-09-29(via やねうらおさんとこ)
C#はバランスと取れた良い言語ですが、あえて欠点を挙げると型推論がイマイチですよね。
using System; using System.Collections.Generic; module M { static Main() : void { // C#のvarに近いが初期化は必須ではない // mutable x = null;もOK。この場合、代入は参照型のみになる mutable x; try { // 適当なコード x = Dictionary.[int, string](); x[10] = "ten"; } catch { | x is Exception => Console.WriteLine(x); } finally { match (x) { | y is IDisposable => y.Dispose() | _ => (); } } } }
カスタム属性が見えるモノ
最近知ったショックなこと。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Sample { [AttributeUsage(AttributeTargets.Class)] class MyAttribute : Attribute { public string Name { private set; get; } public MyAttribute (string name) { Name = name; } } // _nameが参照できる!? [My(_name)] class Program { private const string _name = "mei"; static void Main (string[] args) { } } }
C#が登場してからずっと使ってきているのに、カスタム属性がクラス修飾なしでフィールドにアクセス出来ることを知りませんでした。(^^;
RubyのCycle
ネットを見ていて、true, false, true...と繰り返すコードについての話題があり、そこでRubyにcycleなるものがあることを知りました。Rubyに対するアンテナが低くて・・・(^^;
C#のEnumerableクラスには同様のものはありませんが、自分で書くとこんな感じでしょうか?
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Sample { static class Program { public static IEnumerable<T> Cycle<T> (this IEnumerable<T> e) { while (e.AsEnumerable().Any()) foreach (var v in e.AsEnumerable()) yield return v; } static void Main (string args) { // 空の配列に対しては何もしない foreach (var t in new bool[0].Cycle ()) Console.WriteLine (t); // Ture False True ... foreach (var t in new { true,false }.Cycle ()) Console.WriteLine (t); } } } /* 結果 True False True ... */
あまり出番は無さそうですが、ちょっと面白かったので。