Well, it works. You invoke like this:
. .\Library.DataClass.ps1
class MyClass {
field ([string]) S
field ([int]) I
} > MyClass.cs
And then you can write:
MyClass mc = new MyClass.Builder().SetS("xxx").ToMyClass();
MyClass mc2 = new MyClass.Builder(mc).SetS("yyy").ToMyClass();
And the output looks like:
internal partial class MyClass
{
public readonly System.String S;
public readonly System.Int32 I;
public MyClass(string S, int I)
{
this.S = S;
this.I = I;
}
public class Builder
{
public string S;
public int I;
public Builder()
{
}
public Builder(MyClass value)
{
this.S = value.S;
this.I = value.I;
}
public virtual MyClass ToMyClass()
{
return new MyClass(this.S, this.I);
}
public virtual Builder SetS(string value)
{
this.S = value;
return this;
}
public virtual Builder SetI(int value)
{
this.I = value;
return this;
}
}
}
The main issue is that the ToMyClass and Set* methods are virtual. I think I may be hitting a bug in PowerShell, but I'm still researching it. For now, this will have to do.
Here's the implementation of Library.DataClass.ps1:
function Class
{
param (
[String] $name,
[ScriptBlock] $memberScriptBlock
)
$class = New-Object System.CodeDom.CodeTypeDeclaration $name
$class.TypeAttributes = [System.Reflection.TypeAttributes]::NotPublic
$class.IsPartial = $true
$constructor = New-Object System.CodeDom.CodeConstructor
$constructor.Attributes = [System.CodeDom.MemberAttributes]::Public
$class.Members.Add( $constructor ) | Out-Null
$builderClass = New-Object System.CodeDom.CodeTypeDeclaration "Builder"
$class.Members.Add( $builderClass ) | Out-Null
$builderConstructor = New-Object System.CodeDom.CodeConstructor
$builderConstructor.Attributes = [System.CodeDom.MemberAttributes]::Public
$builderClass.Members.Add( $builderConstructor ) | Out-Null
$builderConstructor2 = New-Object System.CodeDom.CodeConstructor
$builderConstructor2.Attributes = [System.CodeDom.MemberAttributes]::Public
$builderConstructor2.Parameters.Add(
(New-Object System.CodeDom.CodeParameterDeclarationExpression( $name, "value" ))
) | Out-Null
$builderClass.Members.Add( $builderConstructor2 ) | Out-Null
$realizeMethod = New-Object System.CodeDom.CodeMemberMethod
$realizeMethod.Attributes = [System.CodeDom.MemberAttributes]::Public
$realizeMethod.Name = "To$name"
$realizeMethod.ReturnType = $name
$ctorExpression = New-Object System.CodeDom.CodeObjectCreateExpression
$ctorExpression.CreateType = New-Object System.CodeDom.CodeTypeReference($name)
$realizeMethod.Statements.Add(
(New-Object System.CodeDom.CodeMethodReturnStatement(
$ctorExpression
))
) | Out-Null
$builderClass.Members.Add( $realizeMethod ) | Out-Null
# return a hash of the CodeDom objects related to
# this field
function field
{
param (
[Type] $type,
[String] $name
)
@{
type = $type
name = $name
readonlyFieldDeclaration = $(
# CodeDom doesn't support 'readonly' fields.
# See http://blogs.msdn.com/bclteam/archive/2005/03/16/396915.aspx
# $field = New-Object System.CodeDom.CodeMemberField($type, $name)
# $field.Attributes = [System.CodeDom.MemberAttributes]::Public
# $field
New-Object System.CodeDom.CodeSnippetTypeMember("`tpublic readonly $type $name;`n")
)
fieldDeclaration = $(
$field = New-Object System.CodeDom.CodeMemberField($type, $name)
$field.Attributes = [System.CodeDom.MemberAttributes]::Public
$field
)
parameter = New-Object System.CodeDom.CodeParameterDeclarationExpression($type, $name)
fieldReference = New-Object System.CodeDom.CodeFieldReferenceExpression(
(New-Object System.CodeDom.CodeThisReferenceExpression),
$name
)
parameterReference = New-Object System.CodeDom.CodeVariableReferenceExpression $name
setMethodName = "Set$name"
}
}
& $memberScriptBlock | foreach {
$class.Members.Add( $_.readonlyFieldDeclaration )
$constructor.Parameters.Add( $_.parameter )
$constructor.Statements.Add(
$(New-Object System.CodeDom.CodeAssignStatement( $_.fieldReference, $_.parameterReference ))
)
$builderConstructor2.Statements.Add(
(New-Object System.CodeDom.CodeAssignStatement(
$_.fieldReference,
(New-Object System.CodeDom.CodeFieldReferenceExpression(
(New-Object System.CodeDom.CodeVariableReferenceExpression "value"),
$_.name
))
))
)
$builderClass.Members.Add( $_.fieldDeclaration ) | Out-Null
$setMethod = New-Object System.CodeDom.CodeMemberMethod
$setMethod.Name = $_.setMethodName
$setMethod.ReturnType = $builderClass.Name
# This should be Public,Final, but that fails for me. Possible PowerShell bug?
$setMethod.Attributes = [System.CodeDom.MemberAttributes] "Public"
$setMethod.Statements.Add(
(New-Object System.CodeDom.CodeAssignStatement(
$_.fieldReference,
(New-Object System.CodeDom.CodeVariableReferenceExpression ("value"))
))
)
$setMethod.Statements.Add(
(New-Object System.CodeDom.CodeMethodReturnStatement(
(New-Object System.CodeDom.CodeThisReferenceExpression)
))
)
$setMethod.Parameters.Add(
(New-Object System.CodeDom.CodeParameterDeclarationExpression( $_.type, "value" ))
)
$builderClass.Members.Add( $setMethod )
$ctorExpression.Parameters.Add( $_.fieldReference )
} | Out-Null
$csharpCodeProvider = New-Object Microsoft.CSharp.CSharpCodeProvider
$sw = New-Object System.IO.StringWriter
$codeGeneratorOptions = New-Object System.CodeDom.Compiler.CodeGeneratorOptions
$codeGeneratorOptions.BracingStyle = "C"
$codeGeneratorOptions.BlankLinesBetweenMembers = $false
$csharpCodeProvider.GenerateCodeFromType( $class, $sw, $codeGeneratorOptions )
$sw.ToString()
}