Click here to Skip to main content
15,885,757 members
Articles / Desktop Programming / WPF

Introducing the Model Thread View Thread Pattern

Rate me:
Please Sign up or sign in to vote.
4.93/5 (69 votes)
1 May 2010BSD14 min read 163.8K   862   172  
Reduce threading code, and increase UI responsiveness with a new pattern extending MVVM.
<#
#region File and License Information
/*
<File>
	<Copyright>Copyright © 2007, Daniel Vaughan. All rights reserved.</Copyright>
	<License>
		Redistribution and use in source and binary forms, with or without
		modification, are permitted provided that the following conditions are met:
			* Redistributions of source code must retain the above copyright
			  notice, this list of conditions and the following disclaimer.
			* Redistributions in binary form must reproduce the above copyright
			  notice, this list of conditions and the following disclaimer in the
			  documentation and/or other materials provided with the distribution.
			* Neither the name of the <organization> nor the
			  names of its contributors may be used to endorse or promote products
			  derived from this software without specific prior written permission.

		THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY
		EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
		WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
		DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
		DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
		(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
		LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
		ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
		(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
		SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	</License>
	<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
	<CreationDate>2009-08-15 11:55:41Z</CreationDate>
	<Version>2.0</Version>
</File>
*/
#endregion
#>

<#@ template language="C#" hostSpecific="true" #>

<#@ assembly name="System.Core" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Globalization" #>
// ReSharper disable PossibleNullReferenceException
/*
This code was automatically generated by Daniel Vaughan's metadata generator.
Changes to this file may be lost if regeneration occurs.
http://danielvaughan.orpius.com
*/
<#
	if (supportXamlBinding)
	{
		WriteLine("using System.Windows;");	
	}
#>
using System;
using System.Linq;
using System.Linq.Expressions;

<# if (supportObfuscation) { #>

namespace DanielVaughan.Metadata
{	
	static class ObfuscatedNameResolver
	{
		static readonly System.Collections.Generic.Dictionary<string, string> obfuscatedNames = new System.Collections.Generic.Dictionary<string, string>();
		
#if !SILVERLIGHT
		static readonly System.Threading.ReaderWriterLockSlim membersLock = new System.Threading.ReaderWriterLockSlim();
		
		public static string GetObfuscatedName(string preobfuscationName, Func<string, string> action)
		{
			membersLock.EnterUpgradeableReadLock();
			string result;
			try
			{
				if (!obfuscatedNames.TryGetValue(preobfuscationName, out result))
				{
					membersLock.EnterWriteLock();
					try
					{
						if (!obfuscatedNames.TryGetValue(preobfuscationName, out result))
						{
							result = action(preobfuscationName);
							obfuscatedNames[preobfuscationName] = result;
						}
					}
					finally
					{
						membersLock.ExitWriteLock();
					}
				}
				return result;
			}
			finally
			{
				membersLock.ExitUpgradeableReadLock();
			}
		}
#else
		static readonly object membersLock = new object();
		
		public static string GetObfuscatedName(string preobfuscationName, Func<string, string> action)
		{
			string result;
			if (!obfuscatedNames.TryGetValue(preobfuscationName, out result))
			{
				lock (membersLock)
				{
					if (!obfuscatedNames.TryGetValue(preobfuscationName, out result))
					{
						result = action(preobfuscationName);
						obfuscatedNames[preobfuscationName] = result;
					}				
				}
			}
			return result;
		}
#endif
	}
}
<# } #>

<#
	Process();
#>
// ReSharper restore PossibleNullReferenceException
<#+
	/// <summary>
	/// If <c>true</c> <see cref="System.Windows.PropertyPath"/> properties
	/// will be generated for use in binding path assignment.
	/// </summary>
	const bool supportXamlBinding = true;

	/// <summary>
	/// If <c>true</c> key strings will be generated for all keys located 
	/// in XAML files. 
	/// </summary>
	const bool generateXamlKeys = true;

	/// <summary>
	/// XAML metadata is placed into namespaces that are based on the folder structure 
	/// of the project. In order to avoid unintended collisions with type names, a root namespace
	/// can be used. This value can be an empty string, however this is not recommended.
	/// </summary>
	const string xamlRootNamespace = "XamlMetadata";

	/// <summary>
	/// The suffix to use for output class that represent XAML files. 
	/// This is used along with <see cref="generatedClassPrefix"/> 
	/// and <see cref="generatedClassSuffix"/> to name output classes.
	/// A XAML file called Window1.xaml will be represented by a class
	/// called [generatedClassPrefix]Window1[generatedXamlClassSuffix][generatedClassSuffix]
	/// </summary>
	const string generatedXamlClassSuffix = "Xaml";

	/// <summary>
	/// This is experimental. Generic, out, and ref parameters are not supported. 
	/// If <c>true</c> <see cref="System.Windows.PropertyPath"/> member metadata
	/// will be derived using expression trees. 
	/// Only use if obfuscation will occur.
	/// </summary>
	const bool supportObfuscation = false;

	/// <summary>
	/// The modifier to use when outputting classes.
	/// </summary>
	const string generatedClassAccessModifier = "public";

	/// <summary>
	/// The prefix to use for output class and interface names. 
	/// The combination of this and <see cref="generatedClassSuffix"/> provides 
	/// MetaGen with the ability to identify those classes etc., 
	/// for which it should generated metadata, and to ignore MetaGen generated classes.
	/// </summary>
	const string generatedClassPrefix = "";

	/// <summary>
	/// The suffix to use for output class and interface names. 
	/// The combination of this and <see cref="generatedClassSuffix"/> provides 
	/// MetaGen with the ability to identify those classes etc., 
	/// for which it should generated metadata, and to ignore MetaGen generated classes.
	/// </summary>
	const string generatedClassSuffix = "Metadata";

	/// <summary>
	/// The child namespace in which to place generated items.
	/// If there is a class in MyNamespace namespace, 
	/// the metadata class will be generated
	/// in the MyNamespace.[generatedClassSuffix] namespace. 
	/// This string can be null or empty, in which case a subnamesapce 
	/// will not be created, and generated output will reside 
	/// in the original classes namespace.
	/// </summary>
	const string generatedNamespace = "Metadata";

	/// <summary>
	/// The number of spaces to insert for a one step indent.
	/// </summary>
	const int tabSize = 4;
	
	readonly Regex xClassRegex = new Regex(@"x:Key=""(?<KeyName>\w+?)""", RegexOptions.Compiled);
	string projectName;
	string rootNamespace;
	
	public void Process()
	{
		/* Retrieve the DTE. */
		IServiceProvider hostServiceProvider = (IServiceProvider)Host;
		EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
		/* Retrieve the project in which this template resides. */
		EnvDTE.ProjectItem containingProjectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
		Project project = containingProjectItem.ContainingProject;
		projectName = project.FullName;
		/* TODO: Get default namespace from project file. */
		rootNamespace = project.Name.Replace(" ", string.Empty); 
		
		/* Build the namespace representations, which contain class etc. */
		Dictionary<string, NamespaceBuilder> namespaceBuilders = new Dictionary<string, NamespaceBuilder>();
		foreach (ProjectItem projectItem in project.ProjectItems)
		{
			ProcessProjectItem(projectItem, namespaceBuilders, string.Empty);
		}
		
		/* Finally, write them to the output. */
		foreach (object item in namespaceBuilders.Values)
		{
			WriteLine(item.ToString());
		}	
	}
	
	string processingDirectory = string.Empty;
	
	public void ProcessProjectItem(ProjectItem projectItem,
		Dictionary<string, NamespaceBuilder> namespaceBuilders, string activeNamespace)
	{
		FileCodeModel fileCodeModel = projectItem.FileCodeModel;

		if (fileCodeModel != null)
		{
			foreach (CodeElement codeElement in fileCodeModel.CodeElements)
			{
				WalkElements(codeElement, null, null, namespaceBuilders);
			}
		}
		
		string activeNamespaceCopy = activeNamespace;
		if (string.IsNullOrEmpty(activeNamespaceCopy))
		{
			if (string.IsNullOrEmpty(xamlRootNamespace))
			{
				activeNamespaceCopy = rootNamespace; 
			}
			else
			{
				activeNamespaceCopy = string.Format("{0}.{1}", 
					rootNamespace, xamlRootNamespace);
			}
		}
		
		if (projectItem.ProjectItems != null 
			&& projectItem.ProjectItems.Count > 0)
		{
			string fullPath = projectItem.Properties.Item("FullPath").Value.ToString();
			if (System.IO.Directory.Exists(fullPath))
			{
				string newNamespace = projectItem.Name.Replace(" ", string.Empty); 
				activeNamespaceCopy += "." + newNamespace; 
			}	
		}
	
		string itemName = projectItem.Name; 
		if (generateXamlKeys && itemName.EndsWith(".xaml", true, CultureInfo.InvariantCulture))
		{	
			/* Retrieve or create the namespace builder. */
			NamespaceBuilder namespaceBuilder;

			if (!namespaceBuilders.TryGetValue(activeNamespaceCopy, out namespaceBuilder))
			{
				namespaceBuilder = new NamespaceBuilder(activeNamespaceCopy, null, 0);
				namespaceBuilders[activeNamespaceCopy] = namespaceBuilder;
			}
			
			string fileName = projectItem.get_FileNames(0);
			string text = System.IO.File.ReadAllText(fileName);
			MatchCollection matches = xClassRegex.Matches(text);				

			if (matches.Count > 0)
			{
				string xamlMetadataClassName = ConvertProjectItemNameToTypeOrMemberName(itemName.Substring(0, itemName.Length - 4));				
				var classComments = new List<string> {string.Format("/// <summary>Metadata for XAML {0}</summary>", itemName)};
				XamlBuilder xamlBuiler = new XamlBuilder(xamlMetadataClassName, classComments, 1);
				namespaceBuilder.AddChild(xamlBuiler);
				
				foreach (Match match in matches)
				{
					Group keyGroup = match.Groups["KeyName"];
					string keyName = keyGroup.Value;
					var keyComments = new List<string> {string.Format("/// <summary>Represents x:Key=\"{0}\"/></summary>", keyName)};
					xamlBuiler.AddChild(new XamlKeyBuilder(keyName, keyComments));
				}
			}
		}

		if (projectItem.ProjectItems != null)
		{
			foreach (ProjectItem childItem in projectItem.ProjectItems)
			{
				ProcessProjectItem(childItem, namespaceBuilders, activeNamespaceCopy);
			}
		}
	}
	
	Regex codeNameRegex = new Regex(@"\W", RegexOptions.Compiled);
	
	string ConvertProjectItemNameToTypeOrMemberName(string name)
	{
		return codeNameRegex.Replace(name, string.Empty);
	}
	
	string FormatFullNameForClassName(string fullName)
	{
		//string projectName = containingProjectItem.Document.FullName;
		//throw new Exception(projectName);
		return fullName.Replace("/", ".");
	}

	int indent;

	public void WalkElements(CodeElement codeElement, CodeElement parent,
		BuilderBase parentContainer, Dictionary<string, NamespaceBuilder> namespaceBuilders)
	{
		indent++;
		CodeElements codeElements;

		if (parentContainer == null)
		{
			NamespaceBuilder builder;
			string name = "global";
			if (!namespaceBuilders.TryGetValue(name, out builder))
			{
				builder = new NamespaceBuilder(name, null, 0);
				namespaceBuilders[name] = builder;
			}
			parentContainer = builder;
		}

		switch (codeElement.Kind)
		{
			/* Process namespaces. */
			case vsCMElement.vsCMElementNamespace:
				{
					CodeNamespace codeNamespace = (CodeNamespace)codeElement;
					string name = codeNamespace.FullName;
					if (!string.IsNullOrEmpty(generatedNamespace)
							&& name.EndsWith(generatedNamespace))
					{
						break;
					}

					NamespaceBuilder builder;

					if (!namespaceBuilders.TryGetValue(name, out builder))
					{
						builder = new NamespaceBuilder(name, null, 0);
						namespaceBuilders[name] = builder;
					}

					codeElements = codeNamespace.Members;
					foreach (CodeElement element in codeElements)
					{
						WalkElements(element, codeElement, builder, namespaceBuilders);
					}
					break;
				}
			/* Process classes */
			case vsCMElement.vsCMElementClass:
				{
					CodeClass codeClass = (CodeClass)codeElement;
					string name = codeClass.Name;
					if (!string.IsNullOrEmpty(generatedNamespace)
						&& codeClass.FullName.EndsWith(generatedNamespace)
						|| (name.StartsWith(generatedClassPrefix) && name.EndsWith(generatedClassSuffix)))
					{
						break;
					}

					/* If obfuscation mode and the class in internal, 
						then break as it's not supported. */
					if (supportObfuscation && codeClass.Access == vsCMAccess.vsCMAccessPrivate)
					{
						break;
					}

					List<string> comments = new List<string>();
					comments.Add(string.Format("/// <summary>Metadata for class <see cref=\"{0}\"/></summary>", codeClass.FullName));

					BuilderBase builder;
					if (!parentContainer.Children.TryGetValue(name, out builder))
					{
						builder = new ClassBuilder(name, comments, indent);
						parentContainer.Children[name] = builder;
					}
					codeElements = codeClass.Members;
					if (codeElements != null)
					{
						foreach (CodeElement ce in codeElements)
						{
							WalkElements(ce, codeElement, builder, namespaceBuilders);
						}
					}
					break;
				}
			/* Process interfaces. */
			case vsCMElement.vsCMElementInterface:
				{
					CodeInterface codeInterface = (CodeInterface)codeElement;
					string name = codeInterface.Name;
					if (name.StartsWith(generatedClassPrefix) && name.EndsWith(generatedClassSuffix))
					{
						break;
					}
					List<string> comments = new List<string>();
					string commentName = FormatTypeNameForComment(codeInterface.FullName);
					comments.Add(string.Format("/// <summary>Metadata for interface <see cref=\"{0}\"/></summary>", commentName));
					InterfaceBuilder builder = new InterfaceBuilder(name, comments, indent);
					parentContainer.AddChild(builder);

					codeElements = codeInterface.Members;
					if (codeElements != null)
					{
						foreach (CodeElement ce in codeElements)
						{
							WalkElements(ce, codeElement, builder, namespaceBuilders);
						}
					}
					break;
				}
			/* Process methods */
			case vsCMElement.vsCMElementFunction:
				{
					CodeFunction codeFunction = (CodeFunction)codeElement;
					if (codeFunction.Name == parentContainer.Name
						|| codeFunction.Name == "ToString"
						|| codeFunction.Name == "Equals"
						|| codeFunction.Name == "GetHashCode"
						|| codeFunction.Name == "GetType"
						|| codeFunction.Name == "MemberwiseClone"
						|| codeFunction.Name == "ReferenceEquals"
						|| codeFunction.Name.StartsWith("~")
						|| codeFunction.Name.StartsWith("operator "))
					{
						break;
					}
					var parentBuilder = (ClassBuilder)parentContainer;
					parentBuilder.AddMember(codeFunction);
					break;
				}
			/* Process properties. */
			case vsCMElement.vsCMElementProperty:
				{
					var codeProperty = (CodeProperty)codeElement;
					if (codeProperty.Name != "this")
					{
						var parentBuilder = (ClassBuilder)parentContainer;
						parentBuilder.AddMember(codeProperty);
					}
					break;
				}
			/* Process fields. */
			case vsCMElement.vsCMElementVariable:
				{
					var codeVariable = (CodeVariable)codeElement;
					var parentBuilder = (ClassBuilder)parentContainer;
					parentBuilder.AddMember(codeVariable);
					break;
				}
		}
		indent--;
	}

	static string FormatTypeNameForComment(string typeName)
	{
		return typeName.Replace('<', '{').Replace('>', '}');
	}
	
	static readonly Regex replaceGenerics = new Regex("<.*>", RegexOptions.Compiled);
	
	static string FormatNameForProperty(string name)
	{
		string result = name.Replace('.', '_');
		result = replaceGenerics.Replace(result, "Generic");
		return result;
	}


	/// <summary>
	/// The base class for all project item representations.
	/// </summary>
	public abstract class BuilderBase
	{
		string name;
		public string Name
		{
			get
			{
				return name;
			}
		}

		List<string> comments;
		public List<string> Comments
		{
			get
			{
				return comments;
			}
		}

		int indent;
		public int Indent
		{
			get
			{
				return indent;
			}
			set
			{
				indent = value;
			}
		}

		protected BuilderBase(string name, List<string> comments)
		{
			this.name = name;
			this.comments = comments;
		}

		Dictionary<string, BuilderBase> children = new Dictionary<string, BuilderBase>();

		public Dictionary<string, BuilderBase> Children
		{
			get
			{
				return children;
			}
		}

		public virtual void AddChild(BuilderBase obj)
		{
			if (children.ContainsKey(obj.Name))
			{
				return;
			}
			children.Add(obj.Name, obj);
		}

		public override int GetHashCode()
		{
			return Name != null ? Name.GetHashCode() : 0;
		}
	}

	/// <summary>
	/// Represents a namespace within a project and is a container
	/// for classes.
	/// </summary>
	public sealed class NamespaceBuilder : BuilderBase
	{
		public NamespaceBuilder(string name, List<string> comments, int indent)
			: base(name, comments)
		{
		}

		public override string ToString()
		{
			string indentString = string.Empty.PadLeft(Indent);
			StringBuilder sb = new StringBuilder();
			bool global = Name == "global";
			if (!global)
			{
				sb.Append("namespace ");
				sb.Append(Name);
				if (!string.IsNullOrEmpty(generatedNamespace))
				{
					sb.Append('.');
					sb.AppendLine(generatedNamespace);
				}
				else
				{
					sb.AppendLine();
				}
				sb.AppendLine("{");
			}

			foreach (BuilderBase item in Children.Values)
			{
				item.Indent = Indent + tabSize;
				sb.AppendLine(item.ToString());
			}

			if (!global)
			{
				sb.AppendLine("}");
			}
			return sb.ToString();
		}
	}
	
	/// <summary>
	/// Represents a class within a project.
	/// </summary>
	class XamlBuilder : BuilderBase
	{
		Dictionary<string, PropertyPathBuilder> propertyPaths = new Dictionary<string, PropertyPathBuilder>();

		public XamlBuilder(string name, List<string> comments, int indent)
			: base(name, comments)
		{
		}

		public void AddXClassKeyName(string keyName)
		{
			if (Children.ContainsKey(keyName))
			{
				return; /* An overload. */
			}
			string formattedTypeName = FormatTypeNameForComment(keyName);
			string comment = string.Format("/// <summary>Refers to Key <see cref=\"{0}\"/></summary>", formattedTypeName);
			var comments = new List<string> { comment };
			var builder = new XamlKeyBuilder(keyName, comments);
			Children.Add(keyName, builder);
		}

		public override string ToString()
		{
			string indent1 = string.Empty.PadLeft(Indent);
			string indent2 = string.Empty.PadLeft(Indent + tabSize);
			StringBuilder sb = new StringBuilder();

			/* Write comments. */
			foreach (string line in Comments)
			{
				sb.Append(indent1);
				sb.AppendLine(line);
			}
			sb.Append(indent1);
			sb.Append("public");
			sb.Append(" static class ");
			sb.Append(generatedClassPrefix);
			sb.Append(Name);
			sb.Append(generatedXamlClassSuffix);
			sb.AppendLine(generatedClassSuffix);
			sb.Append(indent1);
			sb.AppendLine("{");

			//sb.Append(indent2);
			//sb.AppendLine("public static class KeyNames");
			//sb.Append(indent2);
			//sb.AppendLine("{");

			foreach (BuilderBase item in Children.Values)
			{
				item.Indent = Indent + 8;
				sb.Append(item.ToString());
				sb.AppendLine();
			}
			//sb.Append(indent2);
			//sb.AppendLine("}");
			sb.AppendLine();

			foreach (PropertyPathBuilder item in propertyPaths.Values)
			{
				item.Indent = Indent + 4;
				sb.Append(item.ToString());
				sb.AppendLine();
			}

			sb.Append(indent1);
			sb.AppendLine("}");

			return sb.ToString();
		}
	}
	
	public sealed class XamlKeyBuilder : MemberBuilder
	{
		public XamlKeyBuilder(string keyName, List<string> comments)
			: base(keyName, comments)
		{
		}

		protected override void AppendObfuscatedPropertyContent(StringBuilder sb)
		{
			sb.Append("NotSupportedYet");
		}
		
		public override string ToString()
		{
			string indent = string.Empty.PadLeft(Indent);
			
			StringBuilder sb = new StringBuilder();
			foreach (string line in Comments)
			{
				sb.Append(indent);
				sb.AppendLine(line);
			}
			
			string cleanedName = FormatNameForProperty(Name); 
			
			if (!supportObfuscation)
			{
               sb.Append(indent);
               sb.AppendFormat("public static string {0}Key {{ get {{ return \"{1}\"; }} }}", 
                     cleanedName, cleanedName);
               sb.AppendLine();
               return sb.ToString();
			}
			
			string indent2 = string.Empty.PadLeft(Indent + tabSize);
			string indent3 = string.Empty.PadLeft(Indent + 2 * tabSize);
			
			sb.Append(indent);
			sb.Append("public static string ");
			sb.Append(cleanedName);
			sb.AppendLine("Key");
			sb.Append(indent);
			sb.AppendLine("{");
			sb.Append(indent2);
			sb.AppendLine("get");
			sb.Append(indent2);
			sb.AppendLine("{");
			AppendObfuscatedPropertyContent(sb);
			sb.Append(indent2);
			sb.AppendLine("}");
			sb.Append(indent);
			sb.AppendLine("}");
			return sb.ToString();
		}
	}

	/// <summary>
	/// Represents a class within a project.
	/// </summary>
	class ClassBuilder : BuilderBase
	{
		Dictionary<string, PropertyPathBuilder> propertyPaths = new Dictionary<string, PropertyPathBuilder>();

		public ClassBuilder(string name, List<string> comments, int indent)
			: base(name, comments)
		{
		}

		public void AddMember(CodeFunction codeMember)
		{
			string memberName = codeMember.Name;
			if (Children.ContainsKey(memberName))
			{
				return; /* An overload. */
			}
			string formattedTypeName = FormatTypeNameForComment(codeMember.FullName);
			string comment = string.Format("/// <summary>Refers to method <see cref=\"{0}\"/></summary>", formattedTypeName);
			var comments = new List<string> { comment };
			var builder = new MethodBuilder(codeMember, comments);
			Children.Add(memberName, builder);
		}

		public void AddMember(CodeProperty codeMember)
		{
			string memberName = codeMember.Name;
			if (Children.ContainsKey(memberName))
			{
				return; /* An overload. */
			}
			string formattedTypeName = FormatTypeNameForComment(codeMember.FullName);
			string comment = string.Format("/// <summary>Refers to property <see cref=\"{0}\"/></summary>", formattedTypeName);
			var comments = new List<string> { comment };
			var builder = new PropertyBuilder(codeMember, comments);
			Children.Add(memberName, builder);

			if (supportXamlBinding)
			{
				var pathBuilder = new PropertyPathBuilder(codeMember, comments);
				propertyPaths.Add(memberName, pathBuilder);
			}
		}

		public void AddMember(CodeVariable codeMember)
		{
			string memberName = codeMember.Name;
			if (Children.ContainsKey(memberName))
			{
				return; /* An overload. */
			}
			string formattedTypeName = FormatTypeNameForComment(codeMember.FullName);
			string comment = string.Format("/// <summary>Refers to field <see cref=\"{0}\"/></summary>", formattedTypeName);
			var comments = new List<string> { comment };
			var builder = new FieldBuilder(codeMember, comments);
			Children.Add(memberName, builder);
		}

		public override string ToString()
		{
			string indent1 = string.Empty.PadLeft(Indent);
			string indent2 = string.Empty.PadLeft(Indent + tabSize);
			StringBuilder sb = new StringBuilder();

			/* Write comments. */
			foreach (string line in Comments)
			{
				sb.Append(indent1);
				sb.AppendLine(line);
			}
			sb.Append(indent1);
			sb.Append(generatedClassAccessModifier);
			sb.Append(" static class ");
			sb.Append(generatedClassPrefix);
			sb.Append(Name);
			sb.AppendLine(generatedClassSuffix);
			sb.Append(indent1);
			sb.AppendLine("{");

			sb.Append(indent2);
			//sb.AppendLine("public static class MemberNames");
			//sb.Append(indent2);
			//sb.AppendLine("{");

			foreach (BuilderBase item in Children.Values)
			{
				item.Indent = Indent + tabSize;
				sb.Append(item.ToString());
				sb.AppendLine();
			}
			//sb.Append(indent2);
			//sb.AppendLine("}");
			sb.AppendLine();

			foreach (PropertyPathBuilder item in propertyPaths.Values)
			{
				item.Indent = Indent + 4;
				sb.Append(item.ToString());
				sb.AppendLine();
			}

			sb.Append(indent1);
			sb.AppendLine("}");

			return sb.ToString();
		}
	}

	/// <summary>
	/// Represents an interface within a project.
	/// </summary>
	sealed class InterfaceBuilder : ClassBuilder
	{
		public InterfaceBuilder(string name, List<string> comments, int indent)
			: base(name, comments, indent)
		{
		}
	}

	/// <summary>
	/// Represents a property, method, or field.
	/// </summary>
	public abstract class MemberBuilder : BuilderBase
	{
		public MemberBuilder(string name, List<string> comments)
			: base(name, comments)
		{
		}

		protected abstract void AppendObfuscatedPropertyContent(StringBuilder sb);

		public override string ToString()
		{
			string indent = string.Empty.PadLeft(Indent);
			string indent2 = string.Empty.PadLeft(Indent + tabSize);

			StringBuilder sb = new StringBuilder();
			foreach (string line in Comments)
			{
				sb.Append(indent);
				sb.AppendLine(line);
			}

			sb.Append(indent);
			string cleanedName = FormatNameForProperty(Name); 
			
			if (supportObfuscation)
			{					
				sb.Append("public static string ");
				sb.AppendLine(cleanedName);
				sb.Append(indent);
				sb.AppendLine("{");
				sb.Append(indent2);
				sb.AppendLine("get");
				sb.Append(indent2);
				sb.AppendLine("{");
				/* Output property content. */
				AppendObfuscatedPropertyContent(sb);
				sb.AppendLine();
				sb.Append(indent2);
				sb.AppendLine("}");
				sb.Append(indent);
				sb.Append("}");
			}
			else
			{
				sb.AppendFormat("public const string {0} = \"{1}\";", cleanedName, Name);
			}
			sb.AppendLine();
			return sb.ToString();
		}
	}

	public sealed class MethodBuilder : MemberBuilder
	{
		CodeFunction codeMember;

		public MethodBuilder(CodeFunction codeMember, List<string> comments)
			: base(codeMember.Name, comments)
		{
			this.codeMember = codeMember;
		}

		protected override void AppendObfuscatedPropertyContent(StringBuilder sb)
		{
			string indent3 = string.Empty.PadLeft(Indent + 2 * tabSize);
			string indent4 = string.Empty.PadLeft(Indent + 3 * tabSize);
			string indent5 = string.Empty.PadLeft(Indent + 4 * tabSize);
			
			bool usesGenerics = false;
			foreach (CodeParameter codeParameter in codeMember.Parameters)
			{
				if (codeParameter.FullName.Contains("<"))
				{
					usesGenerics = true;
				}
			}
			
			if (usesGenerics || codeMember.Access != vsCMAccess.vsCMAccessPublic
				&& codeMember.Access != vsCMAccess.vsCMAccessProject)
			{
				sb.Append(indent3);
				sb.AppendFormat("return \"{0}\";", Name);
				return;
			}

			List<string> parameters = new List<string>(codeMember.Parameters.Count);
			foreach (CodeParameter parameter in codeMember.Parameters)
			{
				parameters.Add(parameter.Type.AsFullName);
			}
			var returnType = codeMember.Type.AsFullName;
			bool isAction = string.IsNullOrEmpty(returnType);
			bool hasParameters = parameters.Count > 0;
			
			sb.Append(indent3);
			sb.Append("return ");
			sb.Append("DanielVaughan.Metadata.ObfuscatedNameResolver.GetObfuscatedName(\"");
			sb.Append(codeMember.FullName);
			sb.AppendLine("\",");
			sb.Append(indent4);
			sb.AppendLine("x => {");
			sb.Append(indent5);
			if (isAction)
			{
				sb.Append("Expression<Action");
			}
			else
			{
				sb.Append("Expression<Func");
			}
			
			if (hasParameters || !isAction)
			{
				sb.Append('<');
			}
			
			StringBuilder parameterBuilder = new StringBuilder();
			parameterBuilder.Append('(');
			if (hasParameters)
			{					
				int parametersCount = parameters.Count;
				for (int i = 0; i < parametersCount; i++)
				{
					parameterBuilder.Append("p");						
					parameterBuilder.Append(i);
					string parameter = parameters[i];

					sb.Append(parameter);
					if (i < parametersCount - 1)
					{
						sb.Append(',');
						parameterBuilder.Append(',');
					}
					
				}
				if (parametersCount > 0 && !string.IsNullOrEmpty(returnType))
				{
					sb.Append(',');
				}
			}
			
			parameterBuilder.Append(')');
			string signatureParameters = parameterBuilder.ToString();
			
			if (!isAction) /* Returns a value, therefore always close Func type list. */
			{
				sb.Append(returnType);
				sb.Append('>');
			}
			else if (hasParameters) /* void return type with parameter list. */
			{
				sb.Append('>');
			}

			string dictionaryKey = codeMember.FullName.Substring(0, codeMember.FullName.LastIndexOf('.'));
			/* Static members don't require a default instance for the expression. */
			if (codeMember.IsShared)
			{
				sb.Append("> expression = () => ");
				sb.Append(dictionaryKey);
				sb.Append(".");
			}
			else
			{
				sb.Append("> expression = ");
				sb.Append(signatureParameters);
				sb.Append(" => default(");
				sb.Append(dictionaryKey);
				sb.Append(").");
			}

			sb.Append(Name);
			sb.Append(signatureParameters);
			
			sb.AppendLine(";");
			sb.Append(indent5);
			sb.AppendLine("var body = (MethodCallExpression)expression.Body;");
			sb.Append(indent5);
			sb.AppendLine("return body.Method.Name;");
			sb.Append(indent4);
			sb.AppendLine("});");
		}
	}

	public sealed class PropertyBuilder : MemberBuilder
	{
		readonly CodeProperty codeMember;

		public PropertyBuilder(CodeProperty codeMember, List<string> comments)
			: base(codeMember.Name, comments)
		{
			this.codeMember = codeMember;
		}

		protected override void AppendObfuscatedPropertyContent(StringBuilder sb)
		{
			string indent3 = string.Empty.PadLeft(Indent + 2 * tabSize);
			string indent4 = string.Empty.PadLeft(Indent + 3 * tabSize);
			string indent5 = string.Empty.PadLeft(Indent + 4 * tabSize);

			if (codeMember.Access != vsCMAccess.vsCMAccessPublic
				&& codeMember.Access != vsCMAccess.vsCMAccessProject)
			{
				sb.Append(indent3);
				sb.AppendFormat("return \"{0}\";", Name);
				return;
			}

			var returnType = codeMember.Type.AsFullName;

			sb.Append(indent3);
			sb.Append("return ");
			sb.Append("DanielVaughan.Metadata.ObfuscatedNameResolver.GetObfuscatedName(\"");
			sb.Append(codeMember.FullName);
			sb.AppendLine("\",");
			sb.Append(indent4);
			sb.AppendLine("x => {");
			sb.Append(indent5);
			sb.Append("Expression<Func<");
			sb.Append(returnType);
			string dictionaryKey = codeMember.FullName.Substring(0, codeMember.FullName.LastIndexOf('.'));
			/* Static members don't require a default instance for the expression. */
			if (IsStatic(codeMember))
			{
				sb.Append(">> expression = () => ");
				sb.Append(dictionaryKey);
				sb.Append(".");
			}
			else
			{
				sb.Append(">> expression = () => default(");
				sb.Append(dictionaryKey);
				sb.Append(").");
			}
			
			sb.Append(Name);
			sb.AppendLine(";");
			sb.Append(indent5);
			sb.AppendLine("var body = (MemberExpression)expression.Body;");
			sb.Append(indent5);
			sb.AppendLine("return body.Member.Name;");
			sb.Append(indent4);
			sb.AppendLine("});");
		}
	}

	/// <summary>
	/// Determines whether the specified code element is static.
	/// See this article: http://msdn.microsoft.com/en-us/magazine/cc163757.aspx
	/// </summary>
	/// <param name="codeElement">The code element.</param>
	/// <returns>
	/// 	<c>true</c> if the specified code element is static; otherwise, <c>false</c>.
	/// </returns>
	static bool IsStatic(CodeProperty codeElement)
	{
		if (codeElement.Getter != null)
		{
			return codeElement.Getter.IsShared;
		}

		if (codeElement.Setter != null)
		{
			return codeElement.Setter.IsShared;
		}
		return false;
	}

	public sealed class FieldBuilder : MemberBuilder
	{
		CodeVariable codeMember;

		public FieldBuilder(CodeVariable codeMember, List<string> comments)
			: base(codeMember.Name, comments)
		{
			this.codeMember = codeMember;
		}

		protected override void AppendObfuscatedPropertyContent(StringBuilder sb)
		{
			string indent3 = string.Empty.PadLeft(Indent + 2 * tabSize);
			if (codeMember.Access != vsCMAccess.vsCMAccessPublic
				&& codeMember.Access != vsCMAccess.vsCMAccessProject)
			{
				/* Obfuscated field names that are not public or internal are not supported. */
				sb.Append(indent3);
				sb.AppendFormat("return \"{0}\";", Name);
				return;
			}
			
			string indent4 = string.Empty.PadLeft(Indent + 3 * tabSize);
			string indent5 = string.Empty.PadLeft(Indent + 4 * tabSize);

			var returnType = codeMember.Type.AsFullName;

			sb.Append(indent3);
			sb.Append("return ");
			sb.Append("DanielVaughan.Metadata.ObfuscatedNameResolver.GetObfuscatedName(\"");
			sb.Append(codeMember.FullName);
			sb.AppendLine("\",");
			sb.Append(indent4);
			sb.AppendLine("x => {");
			sb.Append(indent5);
			sb.Append("Expression<Func<");
			sb.Append(returnType);
			string dictionaryKey = codeMember.FullName.Substring(0, codeMember.FullName.LastIndexOf('.'));
			/* Static members don't require a default instance for the expression. */
			if (codeMember.IsShared)
			{
				sb.Append(">> expression = () => ");
				sb.Append(dictionaryKey);
				sb.Append(".");
			}
			else
			{
				sb.Append(">> expression = () => default(");
				sb.Append(dictionaryKey);
				sb.Append(").");
			}

			sb.Append(Name);
			sb.AppendLine(";");
			sb.Append(indent5);
			sb.AppendLine("var body = (MemberExpression)expression.Body;");
			sb.Append(indent5);
			sb.AppendLine("return body.Member.Name;");
			sb.Append(indent4);
			sb.AppendLine("});");

		}
	}

	/// <summary>
	/// Represents a property, method, or field.
	/// </summary>
	public sealed class PropertyPathBuilder : MemberBuilder
	{
		CodeProperty codeMember;

		public PropertyPathBuilder(CodeProperty codeMember, List<string> comments)
			: base(codeMember.Name, comments)
		{
			this.codeMember = codeMember;
		}

		protected override void AppendObfuscatedPropertyContent(StringBuilder sb)
		{
			string indent3 = string.Empty.PadLeft(Indent + 2 * tabSize);
			string indent4 = string.Empty.PadLeft(Indent + 3 * tabSize);
			string indent5 = string.Empty.PadLeft(Indent + 4 * tabSize);

			if (codeMember.Access != vsCMAccess.vsCMAccessPublic
				&& codeMember.Access != vsCMAccess.vsCMAccessProject)
			{
				/* Obfuscated field names that are not public or internal are not supported. */
				sb.Append(indent3);
				sb.AppendFormat("return new PropertyPath(\"{1}\");",
				Name, Name);
				return;
			}
			
			var returnType = codeMember.Type.AsFullName;

			sb.Append(indent3);
			sb.Append("return new PropertyPath(");

			sb.Append("DanielVaughan.Metadata.ObfuscatedNameResolver.GetObfuscatedName(\"");
			sb.Append(codeMember.FullName);
			sb.AppendLine("\",");
			sb.Append(indent4);
			sb.AppendLine("x => {");
			sb.Append(indent5);
			sb.Append("Expression<Func<");
			sb.Append(returnType);				
			string dictionaryKey = codeMember.FullName.Substring(0, codeMember.FullName.LastIndexOf('.'));
			
			/* Static members don't require a default instance for the expression. */
			if (IsStatic(codeMember))
			{
				sb.Append(">> expression = () => ");
				sb.Append(dictionaryKey);
				sb.Append(".");
			}
			else
			{
				sb.Append(">> expression = () => default(");
				sb.Append(dictionaryKey);
				sb.Append(").");
			}
			
			sb.Append(Name);
			sb.AppendLine(";");
			sb.Append(indent5);
			sb.AppendLine("var body = (MemberExpression)expression.Body;");
			sb.Append(indent5);
			sb.AppendLine("return body.Member.Name;");
			sb.Append(indent4);
			sb.AppendLine("}));");
		}

		public override string ToString()
		{
			string indent = string.Empty.PadLeft(Indent);
			
			StringBuilder sb = new StringBuilder();
			foreach (string line in Comments)
			{
				sb.Append(indent);
				sb.AppendLine(line);
			}
			
			string cleanedName = FormatNameForProperty(Name); 
			
			if (!supportObfuscation)
			{
               sb.Append(indent);
               sb.AppendFormat("public static PropertyPath {0}Path {{ get {{ return new PropertyPath(\"{1}\"); }} }}", 
                     cleanedName, cleanedName);
               sb.AppendLine();
               return sb.ToString();
			}
			
			string indent2 = string.Empty.PadLeft(Indent + tabSize);
			string indent3 = string.Empty.PadLeft(Indent + 2 * tabSize);
			
			sb.Append(indent);
			sb.Append("public static PropertyPath ");
			sb.Append(cleanedName);
			sb.AppendLine("Path");
			sb.Append(indent);
			sb.AppendLine("{");
			sb.Append(indent2);
			sb.AppendLine("get");
			sb.Append(indent2);
			sb.AppendLine("{");
			AppendObfuscatedPropertyContent(sb);
			sb.Append(indent2);
			sb.AppendLine("}");
			sb.Append(indent);
			sb.AppendLine("}");
			return sb.ToString();
		}
	}
#>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Engineer
Switzerland Switzerland
Daniel is a former senior engineer in Technology and Research at the Office of the CTO at Microsoft, working on next generation systems.

Previously Daniel was a nine-time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Codon.

Would you like Daniel to bring value to your organisation? Please contact

Blog | Twitter


Xamarin Experts
Windows 10 Experts

Comments and Discussions