How to minimize assembly size on disk of generated code?

I am creating DTOs for an online platform (Dynamics 365). There are fairly strict limits on the maximum build size, and only generated DTOs take up about 80% of that limit. What changes can I make to the code I generate to reduce the amount of disk space that the compiled assembly takes up?

Here is an example that is generated:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Contoso.Xrm.Entities
{

    /// <summary>
    /// Track changes to records for analysis, record keeping, and compliance.
    /// </summary>
    [System.Runtime.Serialization.DataContractAttribute()]
    [Microsoft.Xrm.Sdk.Client.EntityLogicalNameAttribute("audit")]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("CrmSvcUtil", "8.2.1.8676")]
    public partial class Audit : Microsoft.Xrm.Sdk.Entity, System.ComponentModel.INotifyPropertyChanging, System.ComponentModel.INotifyPropertyChanged
    {

        public static class Fields
        {
            public const string Action = "action";
            public const string AttributeMask = "attributemask";
            public const string AuditId = "auditid";
            public const string Id = "auditid";
            public const string CallingUserId = "callinguserid";
            public const string CreatedOn = "createdon";
            public const string ObjectId = "objectid";
            public const string Operation = "operation";
            public const string RegardingObjectId = "regardingobjectid";
            public const string TransactionId = "transactionid";
            public const string UserAdditionalInfo = "useradditionalinfo";
            public const string UserId = "userid";
            public const string lk_audit_callinguserid = "lk_audit_callinguserid";
            public const string lk_audit_userid = "lk_audit_userid";
        }


        /// <summary>
        /// Default Constructor.
        /// </summary>
        [System.Diagnostics.DebuggerNonUserCode()]
        public Audit() : 
                base(EntityLogicalName)
        {
        }

        public const string EntityLogicalName = "audit";

        public const string PrimaryIdAttribute = "auditid";

        public const int EntityTypeCode = 4567;

        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        public event System.ComponentModel.PropertyChangingEventHandler PropertyChanging;

        [System.Diagnostics.DebuggerNonUserCode()]
        private void OnPropertyChanged(string propertyName)
        {
            if ((this.PropertyChanged != null))
            {
                this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }

        [System.Diagnostics.DebuggerNonUserCode()]
        private void OnPropertyChanging(string propertyName)
        {
            if ((this.PropertyChanging != null))
            {
                this.PropertyChanging(this, new System.ComponentModel.PropertyChangingEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Actions the user can perform that cause a change
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("action")]
        public Microsoft.Xrm.Sdk.OptionSetValue Action
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("action");
            }
        }

        /// <summary>
        /// Contains a CSV of the ColumnNumber metadata property of attributes
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("attributemask")]
        public string AttributeMask
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<string>("attributemask");
            }
        }

        /// <summary>
        /// Unique identifier of the auditing instance
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("auditid")]
        public System.Nullable<System.Guid> AuditId
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<System.Nullable<System.Guid>>("auditid");
            }
        }

        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("auditid")]
        public override System.Guid Id
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return base.Id;
            }
            [System.Diagnostics.DebuggerNonUserCode()]
            set
            {
                base.Id = value;
            }
        }

        /// <summary>
        /// Unique identifier of the calling user in case of an impersonated call
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("callinguserid")]
        public Microsoft.Xrm.Sdk.EntityReference CallingUserId
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<Microsoft.Xrm.Sdk.EntityReference>("callinguserid");
            }
        }

        /// <summary>
        /// Date and time when the audit record was created.
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("createdon")]
        public System.Nullable<System.DateTime> CreatedOn
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<System.Nullable<System.DateTime>>("createdon");
            }
        }

        /// <summary>
        /// Unique identifier of the record that is being audited
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("objectid")]
        public Microsoft.Xrm.Sdk.EntityReference ObjectId
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<Microsoft.Xrm.Sdk.EntityReference>("objectid");
            }
        }

        /// <summary>
        /// The action that causes the audit--it will be create, delete, or update
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("operation")]
        public Microsoft.Xrm.Sdk.OptionSetValue Operation
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("operation");
            }
        }

        /// <summary>
        /// Unique identifier of the object with which the record is associated.
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("regardingobjectid")]
        public Microsoft.Xrm.Sdk.EntityReference RegardingObjectId
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<Microsoft.Xrm.Sdk.EntityReference>("regardingobjectid");
            }
            [System.Diagnostics.DebuggerNonUserCode()]
            set
            {
                this.OnPropertyChanging("RegardingObjectId");
                this.SetAttributeValue("regardingobjectid", value);
                this.OnPropertyChanged("RegardingObjectId");
            }
        }

        /// <summary>
        /// Unique identifier for multiple changes that are part of a single operation; this field contains the same GUID for all the audit rows generated in a single transaction
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("transactionid")]
        public System.Nullable<System.Guid> TransactionId
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<System.Nullable<System.Guid>>("transactionid");
            }
        }

        /// <summary>
        /// Additional information associated to the user who caused the change.
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("useradditionalinfo")]
        public string UserAdditionalInfo
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<string>("useradditionalinfo");
            }
            [System.Diagnostics.DebuggerNonUserCode()]
            set
            {
                this.OnPropertyChanging("UserAdditionalInfo");
                this.SetAttributeValue("useradditionalinfo", value);
                this.OnPropertyChanged("UserAdditionalInfo");
            }
        }

        /// <summary>
        /// Unique identifier of the user who caused a change
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("userid")]
        public Microsoft.Xrm.Sdk.EntityReference UserId
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetAttributeValue<Microsoft.Xrm.Sdk.EntityReference>("userid");
            }
        }

        /// <summary>
        /// 1:N userentityinstancedata_audit
        /// </summary>
        [Microsoft.Xrm.Sdk.RelationshipSchemaNameAttribute("userentityinstancedata_audit")]
        public System.Collections.Generic.IEnumerable<Contoso.Xrm.Entities.UserEntityInstanceData> userentityinstancedata_audit
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetRelatedEntities<Contoso.Xrm.Entities.UserEntityInstanceData>("userentityinstancedata_audit", null);
            }
            [System.Diagnostics.DebuggerNonUserCode()]
            set
            {
                this.OnPropertyChanging("userentityinstancedata_audit");
                this.SetRelatedEntities<Contoso.Xrm.Entities.UserEntityInstanceData>("userentityinstancedata_audit", null, value);
                this.OnPropertyChanged("userentityinstancedata_audit");
            }
        }

        /// <summary>
        /// N:1 lk_audit_callinguserid
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("callinguserid")]
        [Microsoft.Xrm.Sdk.RelationshipSchemaNameAttribute("lk_audit_callinguserid")]
        public Contoso.Xrm.Entities.SystemUser lk_audit_callinguserid
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetRelatedEntity<Contoso.Xrm.Entities.SystemUser>("lk_audit_callinguserid", null);
            }
        }

        /// <summary>
        /// N:1 lk_audit_userid
        /// </summary>
        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("userid")]
        [Microsoft.Xrm.Sdk.RelationshipSchemaNameAttribute("lk_audit_userid")]
        public Contoso.Xrm.Entities.SystemUser lk_audit_userid
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return this.GetRelatedEntity<Contoso.Xrm.Entities.SystemUser>("lk_audit_userid", null);
            }
        }

        /// <summary>
        /// Constructor for populating via LINQ queries given a LINQ anonymous type
        /// <param name="anonymousType">LINQ anonymous type.</param>
        /// </summary>
        [System.Diagnostics.DebuggerNonUserCode()]
        public Audit(object anonymousType) : 
                this()
        {
            foreach (var p in anonymousType.GetType().GetProperties())
            {
                var value = p.GetValue(anonymousType, null);
                var name = p.Name.ToLower();

                if (name.EndsWith("enum") && value.GetType().BaseType == typeof(System.Enum))
                {
                    value = new Microsoft.Xrm.Sdk.OptionSetValue((int) value);
                    name = name.Remove(name.Length - "enum".Length);
                }

                switch (name)
                {
                    case "id":
                        base.Id = (System.Guid)value;
                        Attributes["auditid"] = base.Id;
                        break;
                    case "auditid":
                        var id = (System.Nullable<System.Guid>) value;
                        if(id == null){ continue; }
                        base.Id = id.Value;
                        Attributes[name] = base.Id;
                        break;
                    case "formattedvalues":
                        // Add Support for FormattedValues
                        FormattedValues.AddRange((Microsoft.Xrm.Sdk.FormattedValueCollection)value);
                        break;
                    default:
                        Attributes[name] = value;
                        break;
                }
            }
        }

        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("action")]
        public virtual Audit_Action? ActionEnum
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return ((Audit_Action?)(EntityOptionSetEnum.GetEnum(this, "action")));
            }
        }

        [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("operation")]
        public virtual Audit_Operation? OperationEnum
        {
            [System.Diagnostics.DebuggerNonUserCode()]
            get
            {
                return ((Audit_Operation?)(EntityOptionSetEnum.GetEnum(this, "operation")));
            }
        }
    }
}

      

+3


source to share


2 answers


Looking at your assembly DLaB.Xrm.Entities.dll, which is 4.5Mb, it contains about 700 classes, and many of these classes have a large number of properties. The assembly is about 1/3 lines long (if you look at it with a binary editor).

There is nothing obvious to optimize. However, here are some ideas:

  • remove all optional attributes . I would vote for DebuggerNonUserCode, GeneratedCodeAttribute, and possibly EnumMemberAttribute (you will need to check what affects your environment).
  • remove excess . I voted for a static field structure, the lines of which seem to have already been declared elsewhere.
  • factor big classes, 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 will try to get rid of the 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 the standard pattern. Perhaps it could be looked at.

Anyway, I've done some testing using Roslyn CSharpSyntaxRewriter. And with everything I can remove, reduce the build size by 20-25%.

Whatever you decide to shrink, writing a Roslyn correlator seems like a good tool to do it anyway, so here's my example 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 more work).
    static SyntaxNode Nop = SyntaxFactory.ParseExpression(""); // I'm sure there 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);
    }
}

      

+3


source


CodeGolf has a thread with all the tricks to shorten your C # code: https://codegolf.stackexchange.com/questions/173/tips-for-code-golfing-in-c

Update

Are fewer characters translated into less compiled bytes on disk?

In fact, I don't think it is possible if it is not.

Unfortunately, I have weighed in on the bad example, because small is actually bigger, and yet it proves that you can minimize the Golf code footprint . Not as effective as Simon's approach, though. [/ P>

Small:

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0;i< 5;i++)
        {
            Console.Write(i+"");
        }
    }
}

      



= Size: 5.00 KB (5,120 bytes)

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 29 (0x1d)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] int32 i
    )

    IL_0000: ldc.i4.0
    IL_0001: stloc.0
    IL_0002: br.s IL_0018
    // loop start (head: IL_0018)
        IL_0004: ldloc.0
        IL_0005: box [mscorlib]System.Int32
        IL_000a: call string [mscorlib]System.String::Concat(object)
        IL_000f: call void [mscorlib]System.Console::Write(string)
        IL_0014: ldloc.0
        IL_0015: ldc.i4.1
        IL_0016: add
        IL_0017: stloc.0

        IL_0018: ldloc.0
        IL_0019: ldc.i4.5
        IL_001a: blt.s IL_0004
    // end loop

    IL_001c: ret
} // end of method Program::Main

      

Large:

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0;i< 5;i++)
        {
            Console.Write(i.ToString());
        }
    }
}

      

= Size: 4.50 KB (4,608 bytes)

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 25 (0x19)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] int32
    )

    IL_0000: ldc.i4.0
    IL_0001: stloc.0
    IL_0002: br.s IL_0014
    // loop start (head: IL_0014)
        IL_0004: ldloca.s 0
        IL_0006: call instance string [mscorlib]System.Int32::ToString()
        IL_000b: call void [mscorlib]System.Console::Write(string)
        IL_0010: ldloc.0
        IL_0011: ldc.i4.1
        IL_0012: add
        IL_0013: stloc.0

        IL_0014: ldloc.0
        IL_0015: ldc.i4.5
        IL_0016: blt.s IL_0004
    // end loop

    IL_0018: ret
} // end of method Program::Main

      

At the very least, we can see that this compression approach makes little difference.

0


source







All Articles