デシリアライズではまった

次のサンプルコードのMyMapクラスのようなクラスをファイルにシリアライズしてデシリアライズすると、デシリアライズ用のコンストラクタの中で_entriesの要素が全部(null,null)になってしまう。entry.Item1とentry.Item2を使って処理をしていたので、実行時エラーになっていた。そしてその理由が分からず数週間悩んだ。
Testメソッドで、デシリアライズ済みのオブジェクトを操作するときは、_entriesには正しい値が入っているので、ちゃんとTryGetValueを実行できている。entry.Item1やentry.Item2に依存した何らかの初期化処理をしたい場合、どうしたら良いのかな?

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleApplication1
{
  [Serializable]
  class MyString : ISerializable
  {
    string _value;

    public MyString(string str)
    {
      _value = str;
    }

    protected MyString(SerializationInfo info, StreamingContext context)
    {
      _value = info.GetString("Value");
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      info.AddValue("Value", _value);
    }

    public string Value
    {
      get { return _value; }
      set { _value = value; }
    }

    public override string ToString()
    {
      return Value;
    }
  }

  [Serializable]
  class MyMap : ISerializable
  {
    int _n;

    IList<Tuple<MyString, MyString>> _entries;

    public MyMap()
    {
      _n = 0;
      _entries = new List<Tuple<MyString, MyString>>();
    }

    protected MyMap(SerializationInfo info, StreamingContext context)
    {
      _n = info.GetInt32("N");
      Console.WriteLine(_n);

      _entries = (IList<Tuple<MyString, MyString>>)info.GetValue("Entries", typeof(IList<Tuple<MyString, MyString>>));
      foreach (var entry in _entries)
      {
        // なぜかすべてのTupleが(null, null)になっている。
        Console.WriteLine("{0} -> {1}", entry.Item1, entry.Item2);
      }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      info.AddValue("N", _n);
      info.AddValue("Entries", _entries);
    }

    public void Add(MyString val1, MyString val2)
    {
      Tuple<MyString, MyString> entry = Tuple.Create(val1, val2);
      _entries.Add(entry);
    }

    public bool TryGetValue(MyString key, out MyString result)
    {
      foreach (Tuple<MyString, MyString> entry in _entries)
      {
        if (entry.Item1.Value == key.Value)
        {
          result = entry.Item2;
          return true;
        }
      }
      result = null;
      return false;
    }

    public int N
    {
      get { return _n; }
      set { _n = value; }
    }
  }

  class Program
  {
    static MyString Key = new MyString("bar");

    // 【要修正】ファイル出力先
    const string FilePath = @"C:\Users\foobar\Desktop\a.out";

    static void Main(string[] args)
    {
      MyMap map = new MyMap();
      map.Add(new MyString("foo"), new MyString("hoge"));
      map.Add(new MyString("bar"), new MyString("fuga"));
      map.Add(new MyString("baz"), new MyString("piyo"));
      map.N = 42;

      // シリアライズ → デシリアライズ
      Test(map);
    }

    static void Test(MyMap obj)
    {
      MyString result1 = null;
      obj.TryGetValue(Key, out result1);
      System.Console.WriteLine(result1);

      Stream s = Serialize(obj);
      MyMap tmp = Deserialize();

      MyString result2 = null;
      tmp.TryGetValue(Key, out result2);
      System.Console.WriteLine(result2);
    }

    static Stream Serialize(MyMap obj)
    {
      Stream s = null;
      try
      {
        s = new FileStream(FilePath, FileMode.Create);
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(s, obj);
      }
      catch (Exception)
      {
        throw;
      }
      finally
      {
        if (s != null)
        {
          s.Close();
        }
      }

      return s;
    }

    static MyMap Deserialize()
    {
      MyMap ret = null;
      Stream s = null;
      try
      {
        s = new FileStream(FilePath, FileMode.Open);
        s.Position = 0;
        BinaryFormatter bf = new BinaryFormatter();
        ret = (MyMap)bf.Deserialize(s);
      }
      catch (Exception)
      {
        throw;
      }
      finally
      {
        if (s != null)
        {
          s.Close();
        }
      }

      return ret;
    }
  }
}

Excelで列番号を英文字に変換する方法

http://support.microsoft.com/kb/833402/ja にあるけど、これってA〜Zを使った26進数表現だよね、たぶん。A〜Zを使った26進数表現だと思ってたけど違った*1
(念のため魚拓: http://megalodon.jp/2014-0628-2058-19/support.microsoft.com/kb/833402/ja

Function ConvertToLetter(iCol As Integer) As String
   Dim iAlpha As Integer
   Dim iRemainder As Integer
   iAlpha = Int((iCol - 1) / 26)
   iRemainder = iCol - (iAlpha * 26)
   If iAlpha > 0 Then
      ConvertToLetter = Chr(iAlpha + 64)
   End If
   If iRemainder > 0 Then
      ConvertToLetter = ConvertToLetter & Chr(iRemainder + 64)
   End If
End Function

*1:2016/5/26追記

Function内部でHiddenプロパティが効かない件

次のようなコードを書いていた

Function hidenUnusedColumn() As Integer
  Dim rangeStr As String
  rangeStr = 【非表示にする列の範囲を求める関数を実行】
  Columns(rangeStr).Hidden = True
  hidenUnusedColumn = 0
End Function

SubじゃなくてFunctionにしているのは、特に意図はない。


rangeStrは正しく求められている。けどHiddenプロパティが動いてくれない。
数時間悩んだ。
FunctionじゃなくてSubで定義したらちゃんと動いた。


FunctionとSubって、戻り値を返せるかどうかの違いだけだと思ってたけど違うのかな?
Sub内でしか実行できない手続きって何だろう?
逆にFunctionでしか実行できないものってあるのかな。

カレントスキーマの全テーブルの全カラムを一覧するSQL

次の情報も一緒に取得。

  • カラムの型
  • 主キーかどうか
  • NOT NULL制約列かどうか
SELECT
    A.TABLE_NAME
   ,A.COLUMN_NAME
   ,A.COLUMN_ID
   ,A.DATA_TYPE
   ,A.DATA_LENGTH
   ,A.DATA_PRECISION
   ,A.DATA_SCALE
   ,SQ1.CONSTRAINT_TYPE
/*
   ,CASE NVL(SQ1.CONSTRAINT_TYPE,'')
      WHEN 'P' THEN 1
      ELSE 0
    END PKEY
*/
   ,A.NULLABLE
/*
   ,CASE NVL(A.NULLABLE,'')
      WHEN 'N' THEN 1
      ELSE 0
    END NOTNULL
*/
FROM
    USER_TAB_COLUMNS A
   ,(SELECT
        B.TABLE_NAME
       ,B.COLUMN_NAME
       ,C.CONSTRAINT_TYPE
    FROM
        USER_CONS_COLUMNS B
       ,USER_CONSTRAINTS C
    WHERE
        B.OWNER = C.OWNER
    AND B.TABLE_NAME = C.TABLE_NAME
    AND B.CONSTRAINT_NAME = C.CONSTRAINT_NAME
    AND C.CONSTRAINT_TYPE = 'P'
    ) SQ1
WHERE
    A.TABLE_NAME = SQ1.TABLE_NAME(+)
AND A.COLUMN_NAME = SQ1.COLUMN_NAME(+)
ORDER BY
    A.TABLE_NAME
   ,A.COLUMN_ID

図形を拡張メタファイル形式で貼り付ける

Excelで図形(テキストボックス)を多用した書類を作っていざ印刷してみると、印刷結果の図形の大きさが編集の見た目と全然違って困った。
たくさんのテキストボックスを直線でつないで関係性を表すための図だったけど、テキストボックスの大きさが変わってしまい、テキストボックス同士や直線との位置関係が崩れてしまう。
印刷プレビューで見てみると確かにレイアウトが崩れて表示される。編集中の画面はズレてない。
ということで調べてみた。図として貼り付けてやれば編集中の見た目と印刷プレビューが変わることがない、みたいな情報を見つけたのでやってみると、確かにズレなくなった。
しかし、問題が。テキストボックスはたくさん(数百個)ある。手で「形式を選択して貼り付け」なんてやってられない。
そんなこんなで、シート内の図形を、別のシートに拡張メタファイルで貼り付けるExcel VBAを書いたのでメモっとく。

' Book1.xlsm の Sheet1 にある図形を Sheet2 に拡張メタファイル形式で貼り付ける
Sub ShapeCopyEmf()
  Dim myShape As Variant
  Worksheets("Sheet2").Select
  For Each myShape In Workbooks("Book1.xlsm").Worksheets("Sheet1").Shapes
    myShape.Copy
    ActiveSheet.PasteSpecial Format:="図 (拡張メタファイル)"
    Selection.Top = myShape.Top
    Selection.Left = myShape.Left
  Next
End Sub

ループでEval(その1)

evalを再帰で書くとすぐStackOverflowExceptionで死んじゃうので、ループで書いてみようという試み。
単純に走査するだけのコードを前回のエントリで書いたので、それを使いつつ。
こんな感じ。

using System;
using System.Collections.Generic;

abstract class SExp { }

class Pair : SExp
{
  public SExp Car { get; set; }
  public SExp Cdr { get; set; }

  public Pair(SExp car, SExp cdr)
  {
    this.Car = car;
    this.Cdr = cdr;
  }

  public override string ToString()
  {
    return "(" + this.Car + " . " + this.Cdr + ")";
  }
}

class Atom : SExp
{
  public string Value { get; set; }

  public Atom(string str)
  {
    this.Value = str;
  }

  public override string ToString()
  {
    return this.Value;
  }
}

class Nil : SExp 
{
  public override string ToString()
  {
    return "()";
  }
}

class App
{
  static void Main(string[] args)
  {
    // (a b c)
    SExp exp1 = new Pair(new Atom("a"), new Pair(new Atom("b"), new Pair(new Atom("c"), new Nil())));
    // (a b c . d)
    SExp exp2 = new Pair(new Atom("a"), new Pair(new Atom("b"), new Pair(new Atom("c"), new Atom("d"))));
    // (x y (a b c) z)
    SExp exp3 = new Pair(new Atom("x"), new Pair(new Atom("y"), new Pair(exp1, new Pair(new Atom("z"), new Nil()))));

    SExp result = Scan(exp1);
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");

    result = Scan(exp2);
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");

    result = Scan(exp3);
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");

    result = Scan(new Atom("hoge"));
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");

    result = Scan(new Nil());
    Console.WriteLine("result: " + result);
    Console.WriteLine("==========");
  }

  static SExp Scan(SExp exp)
  {
    Stack<SExp> stk1 = new Stack<SExp>();
    Stack<Frame> stk2 = new Stack<Frame>();
    Frame frame = new Frame();
    int sqn = 0;

    SExp result = null;

    while (true)
    {
      if (exp is Pair)
      {
        SExp car = (exp as Pair).Car;
        SExp cdr = (exp as Pair).Cdr;

        if (cdr is Nil || cdr is Atom)
        {
          frame.State = Frame.StateForEval.LastCellScaned;
        }

        stk1.Push(cdr);
        exp = car;

        if (car is Pair)
        {
          Console.WriteLine("push");
          stk2.Push(frame);
          frame = new Frame();
        }
      }
      else
      {
        Console.WriteLine("eval: " + exp);
        result = exp;
        if (frame.State == Frame.StateForEval.LastCarEvaled)
        {
          frame.State = Frame.StateForEval.CanEval;
        }
        if (frame.State == Frame.StateForEval.LastCellScaned)
        {
          frame.State = Frame.StateForEval.LastCarEvaled;
        }

        if (frame.State == Frame.StateForEval.CanEval)
        {
          if (frame.List is Nil)
          {
            frame.List = new Pair(exp, new Nil());
          }
          else
          {
            Pair last = GetLastPair(frame.List as Pair);
            if (exp is Atom)
            {
              last.Cdr = exp;
            }
          }

          // 評価した部分リストは、表示短縮のために E1, E2, E3,... で表記しておく
          sqn++;
          Console.WriteLine("EVAL: " + frame.List + " => E" + sqn);
          Atom a = new Atom("E" + sqn);

          if (stk2.Count == 0)
          {
            result = a;
          }
          else
          {
            Console.WriteLine("pop");
            frame = stk2.Pop();

            if (frame.State == Frame.StateForEval.LastCarEvaled)
            {
              frame.State = Frame.StateForEval.CanEval;
            }
            if (frame.State == Frame.StateForEval.LastCellScaned)
            {
              frame.State = Frame.StateForEval.LastCarEvaled;
            }

            if (frame.List is Nil)
            {
              frame.List = new Pair(exp, new Nil());
            }
            else
            {
              Pair last = GetLastPair(frame.List as Pair);
              last.Cdr = new Pair(a, new Nil());
            }
          }
        }
        else
        {
          if (frame.List is Nil)
          {
            frame.List = new Pair(exp, new Nil());
          }
          else
          {
            Pair last = GetLastPair(frame.List as Pair);
            last.Cdr = new Pair(exp, new Nil());
          }
        }

        if (0 == stk1.Count) break;
        exp = stk1.Pop();
      }
    }

    return result;
  }

  class Frame
  {
    public SExp List { get; set; }
    public StateForEval State { get; set; }

    public Frame()
    {
      List = new Nil();
      State = StateForEval.None;
    }

    public enum StateForEval
    {
      // まだ最後のコンスセルまで来てない
      None,
      // 最後のコンスセル読み込んだ
      LastCellScaned,
      // 最後のコンスセルのcarを処理した
      LastCarEvaled,
      // 最後のコンスセルのcdrを処理した(リストの要素全部を評価した)
      CanEval,
    }
  }

  // 最後のノード取得は番兵でやるべきだろ
  static Pair GetLastPair(Pair list)
  {
    while (list.Cdr is Pair)
    {
      list = list.Cdr as Pair;
    }
    return list;
  }
}

実行結果→Ideone.com - sh0FXd - Online C# Compiler & Debugging Tool
スタック2個使ったけど、大丈夫なのかな。変な事してないかな...。

  • 構文木を走査するためのスタック(stk1)
  • リストの評価がどこまで終わってるか覚えておくためのスタック(stk2)

今回は手続きだけを想定して書いたけど、構文(defineやlambdaなど)は、それ独自の評価ルールがあるから、どげんかせんといかん。