How To Minimize Assembly Size On Disk Of Generated Code?
See the question and my original answer on StackOverflowLooking at your DLaB.Xrm.Entities.dll assembly, which is 4.5Mb, it contains around 700 classes, and many of these classes have a big numbers of properties. The assembly contains about 1/3 of strings (if you look at it with a binary editor).
There's nothing obvious to optimize. However, here are some ideas:
- remove all optional attributes. I'd vote for DebuggerNonUserCode, GeneratedCodeAttribute and possibly EnumMemberAttribute (you'll have to test what is the impact in your environment).
- remove what's redundant. I'd vote for the static Fields structure whose strings seem to already be declared elsewhere.
- factor the big classes more, for example, this is a common pattern:
public partial class Account : Microsoft.Xrm.Sdk.Entity, System.ComponentModel.INotifyPropertyChanging, System.ComponentModel.INotifyPropertyChanged
{
...
public Microsoft.Xrm.Sdk.OptionSetValue AccountRatingCode
{
[System.Diagnostics.DebuggerNonUserCode()]
get
{
return this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("accountratingcode");
}
[System.Diagnostics.DebuggerNonUserCode()]
set
{
this.OnPropertyChanging("AccountRatingCode");
this.SetAttributeValue("accountratingcode", value);
this.OnPropertyChanged("AccountRatingCode");
}
}
...
}
I'd try to get rid of OnPropxxx calls, something like this:
public partial class Account : MyBaseEntity
{
...
public Microsoft.Xrm.Sdk.OptionSetValue AccountRatingCode
{
get
{
return this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("accountratingcode");
}
set
{
this.SetAttributeValue("accountratingcode", value);
}
}
...
}
Also, the constructor of all entity classes seems to do something that follows a standard pattern. Maybe that could be looked at.
Anyway, I've done some testing using a Roslyn CSharpSyntaxRewriter. And with all that I can remove reduce the size of the assembly by 20-25%.
Whatever you decide to reduce, writing a Roslyn rewriter seems the good tool for this anyway, so here is my sample code:
class Program
{
static void Main(string[] args)
{
Do().Wait();
}
static async Task Do()
{
var ws = MSBuildWorkspace.Create();
var project = await ws.OpenProjectAsync(@"..\..\DLaB.Xrm.Entities\DLaB.Xrm.Entities.csproj"); // initial DLaB.Xrm.Entities project from your github
Task.WaitAll(new List<Task>(project.Documents.Select(Rewrite)).ToArray());
}
static async Task Rewrite(Document doc)
{
var tree = await doc.GetSyntaxTreeAsync();
var root = await tree.GetRootAsync();
var optimizer = new EntitySizeOptimizer();
var result = optimizer.Visit(root);
string dir = @"..\..\DLaB.Xrm.Entities2"; // a copy of DLaB.Xrm.Entities project to compare size
string path = Path.Combine(dir, string.Join(@"\", doc.Folders), doc.Name);
File.WriteAllText(path, result.ToFullString());
Console.WriteLine(path);
}
}
class EntitySizeOptimizer : CSharpSyntaxRewriter
{
// try the MyBaseEntity approach (note I just removed the OnPropXXX calls for testing, I've not created the whole base class, it's more work).
static SyntaxNode Nop = SyntaxFactory.ParseExpression(""); // I'm sure there's something smarter than this...
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
{
string text = node.ToFullString().Trim();
if (text.StartsWith("this.OnPropertyChang")) // bit of a hack...
return Nop;
return base.VisitInvocationExpression(node);
}
// remove Fields
public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
{
if (node.Identifier.ValueText == "Fields")
return null;
return base.VisitStructDeclaration(node);
}
// remove useless attributes
public override SyntaxNode VisitAttributeList(AttributeListSyntax node)
{
var atts = new SeparatedSyntaxList<AttributeSyntax>().AddRange(FilterAttributes(node.Attributes));
if (atts.Count == 0) // nothing left, remove this node
return null;
if (atts.Count == node.Attributes.Count) // no change, don't change the tree
return base.VisitAttributeList(node);
return node.WithAttributes(atts); // rewrite
}
private static IEnumerable<AttributeSyntax> FilterAttributes(IEnumerable<AttributeSyntax> atts)
{
foreach (var att in atts)
{
if (IsSameAttribute(att, typeof(EnumMemberAttribute).FullName))
continue;
if (IsSameAttribute(att, typeof(DebuggerNonUserCodeAttribute).FullName))
continue;
if (IsSameAttribute(att, typeof(GeneratedCodeAttribute).FullName))
continue;
yield return att;
}
}
private static bool IsSameAttribute(AttributeSyntax node, string text)
{
return node.Name is QualifiedNameSyntax qn && IsSameAttribute(text, qn.ToFullString());
}
// note: we could (should?) use Roslyn Semantic Model, but this is faster...
private static bool IsSameAttribute(string att1, string att2)
{
if (att1 == att2)
return true;
string StripAttribute(string att)
{
const string token = "Attribute";
return att.EndsWith(token) ? att.Substring(0, att.Length - token.Length) : att;
}
return StripAttribute(att1) == StripAttribute(att2);
}
}