かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

サービス参照で追加したクラスにIEditableObjectを実装させるT4 Template

Visual Studio 2010でサービス参照を追加したときに作成されるクラスは、INotifyPropertyChangedまで実装してくれてる。
(VS2008のは未確認。実装してたっけか?)

このT4 Template使えばIEditableObjectが動くように出来るかも。
リフレクション使ってるので、速度的にはハードコーディングするのより遅いけど、お手軽なので。
(因みに未実行コードなので、動くかどうかは謎だけど、それっぽいコードが生成されてた)

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml.Linq.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ assembly name="System.Core.dll" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Collections.Generic" #>

<# Init(); #>
<#
foreach (var clazz in classes)
{
#>
namespace <#= ns #>.<#= clazz.Namespace #>
{
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Reflection;
    using System.Linq;
    public partial class <#= clazz.Name #> : IEditableObject
    {
        // プロパティと名前のマップ
        private Dictionary<string, PropertyInfo> _propertyCache;
        // 編集開始時点の値を確保しておくためのマップ
        private Dictionary<string, object> _cacheValues = new Dictionary<string, object>();

        // プロパティと名前のマップを取得する
        private Dictionary<string, PropertyInfo> Properties
        {
            get
            {
                // 一応キャッシュ
                if (_propertyCache == null)
                {
                    _propertyCache = GetType().GetProperties().ToDictionary(p => p.Name);
                }
                return _propertyCache;
            }
        }

        #region IEditableObject Members
        // 編集中かどうかのフラグ
        private bool _isEditing;
        public void BeginEdit()
        {
            if (_isEditing) return;
            // プロパティの現在の値をバックアップ
            foreach (var props in Properties)
            {
                _cacheValues[props.Key] = props.Value.GetValue(this, null);
            }
        }

        public void CancelEdit()
        {
            if (!_isEditing) return;
            _isEditing = false;
            // キャンセルなので、編集開始時点の値に書き戻す
            foreach (var props in Properties)
            {
                props.Value.SetValue(
                    this,
                    _cacheValues[props.Key],
                    null);
            }
            _cacheValues.Clear();
        }

        public void EndEdit()
        {
            if (!_isEditing) return;
            _isEditing = false;
            // 編集確定なので、編集開始時点の値は捨てる
            _cacheValues.Clear();
        }

        #endregion
    }
}
<#
}
#>
<#+
// 名前空間だけは指定しておく
string ns = "Okazuki.WPF.TestApp";
// ここに、Service References以下で作成されたクラスの情報が入る
List<ClassInfo> classes = new List<ClassInfo>();
// クラス
class ClassInfo
{
	public string Namespace { get; set; }
	public string Name { get; set; }
}

public void Init()
{
	// テンプレートのあるフォルダの下のService Referencesの下にあるフォルダを取得
	var path = Path.Combine(Path.GetDirectoryName(Host.TemplateFile), "Service References");
	var dirs = Directory.GetDirectories(path);
	foreach (var dir in dirs)
	{
		// xsdファイルに色々定義されてるので、それを取得
		var files = Directory.GetFiles(dir, "*.xsd");
		ProcessFiles(dir, files);
	}
}
void ProcessFiles(string dir, string[] files)
{
	foreach (var file in files)
	{
		// 多分*1.xsdというファイルにWCFのサービスの引数や戻り値で使うクラスが定義されてる
		if (file.EndsWith("1.xsd"))
		{
			ProcessFile(dir, file);
		}
	}
}
void ProcessFile(string dir, string file)
{
	var elm = XElement.Load(file);
	XNamespace xs = "http://www.w3.org/2001/XMLSchema";
	
	classes.AddRange(
		from complexType in elm.Descendants(xs + "complexType")
		where
			// ArrayOfで始まる奴は配列なので見ない
			!complexType.Attribute("name").Value.StartsWith("ArrayOf")
		select
			new ClassInfo
			{
				Namespace = Path.GetFileName(dir),
				Name = complexType.Attribute("name").Value
			});
}
#>

適当な、WCFサービスを作ってサービス参照に追加してみたら、以下のようなコードが生成されました。

namespace Okazuki.WPF.TestApp.EmployeesManagement
{
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Reflection;
    using System.Linq;
    public partial class Employees : IEditableObject
    {
        // プロパティと名前のマップ
        private Dictionary<string, PropertyInfo> _propertyCache;
        // 編集開始時点の値を確保しておくためのマップ
        private Dictionary<string, object> _cacheValues = new Dictionary<string, object>();

        // プロパティと名前のマップを取得する
        private Dictionary<string, PropertyInfo> Properties
        {
            get
            {
                // 一応キャッシュ
                if (_propertyCache == null)
                {
                    _propertyCache = GetType().GetProperties().ToDictionary(p => p.Name);
                }
                return _propertyCache;
            }
        }

        #region IEditableObject Members
        // 編集中かどうかのフラグ
        private bool _isEditing;
        public void BeginEdit()
        {
            if (_isEditing) return;
            // プロパティの現在の値をバックアップ
            foreach (var props in Properties)
            {
                _cacheValues[props.Key] = props.Value.GetValue(this, null);
            }
        }

        public void CancelEdit()
        {
            if (!_isEditing) return;
            _isEditing = false;
            // キャンセルなので、編集開始時点の値に書き戻す
            foreach (var props in Properties)
            {
                props.Value.SetValue(
                    this,
                    _cacheValues[props.Key],
                    null);
            }
            _cacheValues.Clear();
        }

        public void EndEdit()
        {
            if (!_isEditing) return;
            _isEditing = false;
            // 編集確定なので、編集開始時点の値は捨てる
            _cacheValues.Clear();
        }

        #endregion
    }
}
namespace Okazuki.WPF.TestApp.EmployeesManagement
{
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Reflection;
    using System.Linq;
    public partial class Sample : IEditableObject
    {
        // プロパティと名前のマップ
        private Dictionary<string, PropertyInfo> _propertyCache;
        // 編集開始時点の値を確保しておくためのマップ
        private Dictionary<string, object> _cacheValues = new Dictionary<string, object>();

        // プロパティと名前のマップを取得する
        private Dictionary<string, PropertyInfo> Properties
        {
            get
            {
                // 一応キャッシュ
                if (_propertyCache == null)
                {
                    _propertyCache = GetType().GetProperties().ToDictionary(p => p.Name);
                }
                return _propertyCache;
            }
        }

        #region IEditableObject Members
        // 編集中かどうかのフラグ
        private bool _isEditing;
        public void BeginEdit()
        {
            if (_isEditing) return;
            // プロパティの現在の値をバックアップ
            foreach (var props in Properties)
            {
                _cacheValues[props.Key] = props.Value.GetValue(this, null);
            }
        }

        public void CancelEdit()
        {
            if (!_isEditing) return;
            _isEditing = false;
            // キャンセルなので、編集開始時点の値に書き戻す
            foreach (var props in Properties)
            {
                props.Value.SetValue(
                    this,
                    _cacheValues[props.Key],
                    null);
            }
            _cacheValues.Clear();
        }

        public void EndEdit()
        {
            if (!_isEditing) return;
            _isEditing = false;
            // 編集確定なので、編集開始時点の値は捨てる
            _cacheValues.Clear();
        }

        #endregion
    }
}

本当は、Entityというような名前のクラスを作って、それを継承させようと思ったらサービス参照の追加で生成されるコードが、object型を明示的に継承してるので、怒られた。残念。