I have a T4 Templates project that generates methods to "convert" one Type (class, struct, enum) from a source Assembly its corresponding Type in another. The two types have the identical names and property names, just different namespaces and different types. This may be a re-invented a wheel. Nonetheless, I didn't find any solutions out there immediately, and it felt like a good programming exercise anyway. I'm also not familiar with dynamic languages and though it would be a worthwhile application of the dynamic (System.Dynamic.DynamicObject) keyword.
This following is the .tt file which writes out all the "convert" methods, but most of the work is done in a static class with numerous helper extension methods.
<#@ template language="C#" inherits="CommonT4.BaseTemplate" #>
<#@ Import Namespace="CommonT4" #>
<#@ Import Namespace="System.Collections.Generic" #>
<#@ Import Namespace="System.Text" #>
<#@ Import Namespace="System.Linq" #>
<#@ Import Namespace="System.IO" #>
<#@ Import Namespace="System.Reflection" #>
namespace <#= Namespace #>
{
public static class ConvertExtension
{
<#
//will this work for ValueType (enum, struct)?
foreach (var typeTuple in this.GetConvertTypeMappings(this.SrcAssembly, this.DestAssembly))
{
Type destType = typeTuple.Item2;
Type srcType = typeTuple.Item1;
#>
public static <#=destType.ToGenericTypeFullNameString()#> Convert(this <#=srcType.ToGenericTypeFullNameString() #> src0)
{
dynamic dest0;
dynamic <#=HelperExtensionT4.DynamicVariableNameDeclarationsString(1, MAX_LVL) #>
<#
HelperExtensionT4.WriteConvertType(this, destType, srcType);
#>
return dest0;
}
<#
}
#>
}
}
<#+
public string Namespace;
public static int MAX_LVL = 16;
#>
Notice the first line in every generated convert method will declare a series of dynamic variables, most of which will be reused. The reason I used dynamic keywords was so I could reuse the same variable names, even if the types assigned to those variables change later. This would make this T4 template function "WriteConvertType" easier to finish writing, in my opinion. To clarify, the dynamic keyword is not used anywhere within this helper method - the dynamic keyword will show up in the generated code with all the conversion methods. At the end of the question I'll post a snippet of what the generated code looks like, since it is lengthy. This is the helper class where a recursive extension method "WriteConvertType" is defined.
public static void WriteConvertType(BaseTemplate template, Type destType, Type srcType, Stack<Type> stack = null)
{
if (stack == null)
stack = new Stack<Type>();
if (srcType.IsNullableType())
{
template.WriteLine("dest{0} = new {1}(default({2}));", stack.Count, destType.ToGenericTypeFullNameString(), destType.GetGenericArguments()[0].ToGenericTypeFullNameString());
return;
}
else
template.WriteLine("dest{0} = new {1}();", stack.Count, destType.ToGenericTypeFullNameString());
var commonProperties = from tuple in template.GetCommonConvertProperties(destType, srcType)
select new { desttype = tuple.Item1, srctype = tuple.Item2, Name = tuple.Item3 };
foreach (var common in commonProperties)
{
Type srcElementType;
Type mappedType;
if (common.desttype == common.srctype || template.IsTypeMapped(common.desttype, out mappedType))//base case: would-be return;
template.WriteLine("dest{0}.{1} = src{2}.{3};", stack.Count, common.Name, stack.Count, common.Name);
else if (common.srctype.isGenericEnumerableType(out srcElementType))
{
//get matching generic argument/element Type from dest. Assembly or mscorlib:
Type destElementType = (from dtype in template.DestAssembly.GetTypes().Union(mscorlib.GetTypes())
where dtype.Name == srcElementType.Name
select dtype).First();
template.WriteLine("if (src{0}.{1} != null) {{", stack.Count, common.Name);
template.PushIndent(" ");
template.WriteLine("dest{0}.{1} = new {2}();"
, stack.Count
, common.Name
, common.desttype.ToGenericTypeFullNameString());
//caller checks stack:
if (!stack.Contains(common.desttype))
{
template.WriteLine("for (int i{0} = 0; i{0} < src{0}.{1}.Count; i{0}++)"
, stack.Count
, common.Name);
template.WriteLine("{");
template.PushIndent(" ");
template.WriteLine("src{0} = src{1}.{2}[i{1}];", stack.Count + 1, stack.Count, common.Name);
stack.Push(destElementType);
WriteConvertType(template, destElementType, srcElementType, stack);//, lvl + 1);
stack.Pop();
template.WriteLine("dest{0}.{1}.Add(dest{2});", stack.Count, common.Name, stack.Count + 1);
template.PopIndent();
template.WriteLine("}");//end for
}
template.PopIndent();
template.WriteLine("}");//end if
}
else
{
if (!stack.Contains(common.desttype))
{
template.WriteLine("src{0} = src{1}.{2};", stack.Count + 1, stack.Count, common.Name);//if property type not equal (and not IEnumerable)
template.WriteLine("if (src{0} is {1}) {{", stack.Count + 1, common.srctype.ToGenericTypeFullNameString());//if src != null
template.PushIndent(" ");//CurrentIndent += 1;
stack.Push(common.desttype);
WriteConvertType(template, common.desttype, common.srctype, stack);// lvl + 1);
stack.Pop();
template.WriteLine("dest{0}.{1} = dest{2};", stack.Count, common.Name, stack.Count + 1);//finally, set the property upon return
template.PopIndent();
template.WriteLine("}");
}
}
}
return;
}
Would this be considered excessive use or not an ideal situation for use of the C# dynamic keyword? Does this incur a noticeable performance penalty? Are there any guidelines or patterns/practices for the DLR?
Also, is "Convert" a good title for the method? The purpose of this method does seem to be a convert operation, although it's not really a cast. The purpose will be eventually to marshal the source type that's not serializable by the WCF DataContractSerializer to a destination type that will serialize.
Thanks for reading. I put the source code for this here: http://wcfsilverlighthelper.codeplex.com/
Finally, here is an example of one of the generated methods.
public static Northwind.SL.Model.Product Convert(this Northwind.NET.Model.Product src0)
{
dynamic dest0;
dynamic src1, dest1, src2, dest2, src3, dest3, src4, dest4, src5, dest5, src6, dest6, src7, dest7, src8, dest8, src9, dest9, src10, dest10, src11, dest11, src12, dest12, src13, dest13, src14, dest14, src15, dest15, src16, dest16;
dest0 = new Northwind.SL.Model.Product();
dest0.ID = src0.ID;
src1 = src0.PermissionType;
if (src1 is System.Nullable<Northwind.NET.Security.PermissionTypeEnum>)
{
dest1 = new System.Nullable<Northwind.SL.Security.PermissionTypeEnum>(default(Northwind.SL.Security.PermissionTypeEnum));
dest0.PermissionType = dest1;
}
src1 = src0.SecureName;
if (src1 is Northwind.NET.Security.SecureString)
{
dest1 = new Northwind.SL.Security.SecureString();
src2 = src1.SecurityHandle;
if (src2 is Northwind.NET.Security.SecurityHandle)
{
dest2 = new Northwind.SL.Security.SecurityHandle();
dest2.Domain = src2.Domain;
dest1.SecurityHandle = dest2;
}
dest1.Value = src1.Value;
dest0.SecureName = dest1;
}
dest0.Name = src0.Name;
src1 = src0.SecurityHandle;
if (src1 is Northwind.NET.Security.SecurityHandle)
{
dest1 = new Northwind.SL.Security.SecurityHandle();
dest1.Domain = src1.Domain;
dest0.SecurityHandle = dest1;
}
dest0.SupplierID = src0.SupplierID;
dest0.CategoryID = src0.CategoryID;
dest0.QuantityPerUnit = src0.QuantityPerUnit;
dest0.UnitPrice = src0.UnitPrice;
dest0.UnitsInStock = src0.UnitsInStock;
dest0.UnitsOnOrder = src0.UnitsOnOrder;
dest0.ReorderLevel = src0.ReorderLevel;
dest0.Discontinued = src0.Discontinued;
dest0.RowTimeStamps = src0.RowTimeStamps;
src1 = src0.Supplier;
if (src1 is Northwind.NET.Model.Supplier)
{
dest1 = new Northwind.SL.Model.Supplier();
dest1.ID = src1.ID;
dest1.Name = src1.Name;
src2 = src1.SecureName;
if (src2 is Northwind.NET.Security.SecureString)
{
dest2 = new Northwind.SL.Security.SecureString();
src3 = src2.SecurityHandle;
if (src3 is Northwind.NET.Security.SecurityHandle)
{
dest3 = new Northwind.SL.Security.SecurityHandle();
dest3.Domain = src3.Domain;
dest2.SecurityHandle = dest3;
}
dest2.Value = src2.Value;
dest1.SecureName = dest2;
}
src2 = src1.PermissionType;
if (src2 is System.Nullable<Northwind.NET.Security.PermissionTypeEnum>)
{
dest2 = new System.Nullable<Northwind.SL.Security.PermissionTypeEnum>(default(Northwind.SL.Security.PermissionTypeEnum));
dest1.PermissionType = dest2;
}
dest1.ContactName = src1.ContactName;
dest1.ContactTitle = src1.ContactTitle;
dest1.Address = src1.Address;
dest1.City = src1.City;
dest1.Region = src1.Region;
dest1.PostalCode = src1.PostalCode;
dest1.Country = src1.Country;
dest1.Phone = src1.Phone;
dest1.Fax = src1.Fax;
dest1.HomePage = src1.HomePage;
dest1.RowTimeStamps = src1.RowTimeStamps;
if (src1.Products != null)
{
dest1.Products = new System.Collections.Generic.List<Northwind.SL.Model.Product>();
for (int i1 = 0; i1 < src1.Products.Count; i1++)
{
src2 = src1.Products[i1];
dest2 = new Northwind.SL.Model.Product();
dest2.ID = src2.ID;
src3 = src2.PermissionType;
if (src3 is System.Nullable<Northwind.NET.Security.PermissionTypeEnum>)
{
dest3 = new System.Nullable<Northwind.SL.Security.PermissionTypeEnum>(default(Northwind.SL.Security.PermissionTypeEnum));
dest2.PermissionType = dest3;
}
src3 = src2.SecureName;
if (src3 is Northwind.NET.Security.SecureString)
{
dest3 = new Northwind.SL.Security.SecureString();
src4 = src3.SecurityHandle;
if (src4 is Northwind.NET.Security.SecurityHandle)
{
dest4 = new Northwind.SL.Security.SecurityHandle();
dest4.Domain = src4.Domain;
dest3.SecurityHandle = dest4;
}
dest3.Value = src3.Value;
dest2.SecureName = dest3;
}
dest2.Name = src2.Name;
src3 = src2.SecurityHandle;
if (src3 is Northwind.NET.Security.SecurityHandle)
{
dest3 = new Northwind.SL.Security.SecurityHandle();
dest3.Domain = src3.Domain;
dest2.SecurityHandle = dest3;
}
dest2.SupplierID = src2.SupplierID;
dest2.CategoryID = src2.CategoryID;
dest2.QuantityPerUnit = src2.QuantityPerUnit;
dest2.UnitPrice = src2.UnitPrice;
dest2.UnitsInStock = src2.UnitsInStock;
dest2.UnitsOnOrder = src2.UnitsOnOrder;
dest2.ReorderLevel = src2.ReorderLevel;
dest2.Discontinued = src2.Discontinued;
dest2.RowTimeStamps = src2.RowTimeStamps;
dest1.Products.Add(dest2);
}
}
dest0.Supplier = dest1;
}
return dest0;
}