タグ別アーカイブ: dynamic

DynamicObjectのTry~でfalseを返すと・・・

鍵付きアカウントな方から「bool 返す TryHoge を実装してあげるんだ、なるほど。これってfalseが返ると例外発生させるのかな」と聞かれたので、やってみましょうそうしましょう。

 

こんな適当なDynamicObjectを拵えて実行してみると。

 

予想通り例外発生です。おめでとうございます。

image

広告

DynamicFixedRecord – 固定長文字列を長さで区切って各項目に分割してdynamicにアクセス

ネタ元:neue cc – dynamicとQueryString、或いは無限に不確定なオプション引数について

 

まとめ

dynamicはC#と外の世界を繋ぐためのもの。今日もまた一つ繋いでしまった。それはともかくとして、一番最初、DynamicJsonを実装した頃にも言ったのですが、dynamicはDSL的な側面もあって、普通に楽しめますので、ちょっと頭をひねって活用してみると、また一つ、素敵な世界が待っています。

というわけで、何かやってみようと思っていたところ、twitter上で@kazukさんがこんなことをつぶやいていました。

固定長レコードってのも、C#とは相性が良くないほうのものなので、コネコネしてみました。

  1. using System;
  2. using System.Collections.Specialized;
  3. using System.Dynamic;
  4. using System.Linq;
  5.  
  6. namespace DynamicFixedRecord
  7. {
  8.   public static class StringExtensions
  9.   {
  10.     public static dynamic AsDynamic(this string fixedRecord)
  11.     {
  12.       return new DynamicFixedRecord(fixedRecord);
  13.     }
  14.   }
  15.  
  16.   public class DynamicFixedRecord : DynamicObject
  17.   {
  18.     private NameValueCollection source;
  19.     private string fixedRecord;
  20.  
  21.     public DynamicFixedRecord(string fixedRecord)
  22.     {
  23.       this.source = new NameValueCollection();
  24.       this.fixedRecord = fixedRecord;
  25.     }
  26.  
  27.     public override bool TryGetMember(GetMemberBinder binder, out object result)
  28.     {
  29.       var value = source[binder.Name];
  30.       result = new StringMember(value);
  31.       return true;
  32.     }
  33.  
  34.     public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
  35.     {
  36.       var startIndex = 0;
  37.       source.Clear();
  38.       foreach (var item in binder.CallInfo.ArgumentNames.Zip(args, (key, value) => new { key, value }))
  39.       {
  40.         var length = Int32.Parse(item.value.ToString());
  41.         source.Add(item.key, fixedRecord.Substring(startIndex, length));
  42.         startIndex += length;
  43.       }
  44.  
  45.       result = this;
  46.       return true;
  47.     }
  48.  
  49.     public override bool TryConvert(ConvertBinder binder, out object result)
  50.     {
  51.       if (binder.Type != typeof(string))
  52.       {
  53.         result = null;
  54.         return false;
  55.       }
  56.       else
  57.       {
  58.         result = this.ToString();
  59.         return true;
  60.       }
  61.     }
  62.  
  63.     public override string ToString()
  64.     {
  65.       return string.Join(", ",
  66.         source.Cast<string>().Select(key => key + ":" + source[key]));
  67.     }
  68.   }
  69.  
  70.   class StringMember : DynamicObject
  71.   {
  72.     readonly string value;
  73.  
  74.     public StringMember(string value)
  75.     {
  76.       this.value = value;
  77.     }
  78.  
  79.     public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
  80.     {
  81.       var defaultValue = args.First();
  82.  
  83.       try
  84.       {
  85.         result = (value == null)
  86.             ? defaultValue
  87.             : Convert.ChangeType(value, defaultValue.GetType());
  88.       }
  89.       catch (FormatException)
  90.       {
  91.         result = defaultValue;
  92.       }
  93.  
  94.       return true;
  95.     }
  96.  
  97.     public override bool TryConvert(ConvertBinder binder, out object result)
  98.     {
  99.       try
  100.       {
  101.         var type = (binder.Type.IsGenericType
  102.           && binder.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
  103.             ? binder.Type.GetGenericArguments().First()
  104.             : binder.Type;
  105.  
  106.         result = (value == null)
  107.             ? null
  108.             : type == typeof(DateTime)
  109.               ? DateTime.ParseExact(value,
  110.                 new[] { "yyyyMMdd", "yyyy/MM/dd", "yyMMdd", "yy/MM/dd" },
  111.                 null,
  112.                 System.Globalization.DateTimeStyles.None)
  113.               : Convert.ChangeType(value, binder.Type);
  114.       }
  115.       catch (FormatException)
  116.       {
  117.         result = null;
  118.       }
  119.  
  120.       return true;
  121.     }
  122.  
  123.     public override string ToString()
  124.     {
  125.       return value ?? "";
  126.     }
  127.   }
  128.  
  129.   public class Program
  130.   {
  131.     public static void Main(string[] args)
  132.     {
  133.       var record = "A0120120214".AsDynamic();
  134.  
  135.       record(col1: 1, col2: 2, col3: 8);
  136.  
  137.       string col1 = record.col1;
  138.       Console.WriteLine(col1);
  139.  
  140.       int col2 = record.col2;
  141.       Console.WriteLine(col2);
  142.  
  143.       DateTime col3 = record.col3;
  144.       Console.WriteLine(col3);
  145.  
  146.       Console.WriteLine(record);
  147.  
  148.       // [Results]
  149.       // A
  150.       // 1
  151.       // 2012/02/14 0:00:00
  152.       // col1:A, col2:01, col3:20120214
  153.     }
  154.   }
  155. }

 

半分以上は@neueccさんのコードを流用させていただきました。(さんくす!)

ちょっと工夫した点は次の通り。

  • StringMemberクラスでDateTime型へのConvertを可能にした。(TryConvertメソッド)
  • 名前付き引数は、レコードの各項目を切り出す際の長さを指定するために使うようにした。

くらいでしょうか。

まだ複数行読み込んだ場合などに対応していない状態なので、APIを検討しつつもう少し洗練させてもよさそうですね。

 

なんにせよ、やってみたら案外dynamicを扱うのは簡単でした。(もちろん@neueccさんのおかげですけどね!)

まだまだdynamicな世界の可能性は広がっていそうですので、今後もちょろちょろと試して行ってみるつもりです。

皆さんもぜひやってみてくださいね。

 

[2012/2/16追記]

ソースはGistにも上げてあります。