Создавайте частные сеттеры с помощью CodeDOM

В настоящее время я разрабатываю API, который динамически компилируется в сборку на основе некоторых предопределенных правил, хранящихся в XML-документах.

Мне сложно заставить CodeDOM генерировать свойства с помощью общедоступного получателя и частного установщика, украшенного настраиваемыми атрибутами.

Вот что мне нужно:

[Conditional()]
public E3477 E3477 { get; private set; }

Но я получаю это, что нехорошо, так как я не хочу, чтобы сеттер был открыт публично:

[Conditional()]
public E3477 E3477 
{
    get
    {
    }
    set
    {
    }
}

Это код, который я использую:

var componentRef = string.Format( "E{0}", component.XPathSelectElement( "Element" ).Value );
CodeMemberProperty prop = new CodeMemberProperty();
prop.Name = componentRef;
prop.Type = new CodeTypeReference( componentRef );
prop.HasSet = true;
prop.HasGet = true;
prop.Attributes = MemberAttributes.Public;
CodeAttributeDeclaration conditionalAttr = new CodeAttributeDeclaration( "Conditional" );
prop.CustomAttributes.Add( conditionalAttr );

compositeElementClass.Members.Add( prop );

Возможно ли то, что мне нужно, в CodeDOM?

Сообщество Stack Overflow посоветовало мне использовать CodeDom и создавать сборки вместо использования MSBuild, что я пытался сделать изначально, но не смог заставить его работать.

** РЕДАКТИРОВАТЬ с трудным для чтения кодом, чтобы увидеть, можно ли его упростить **

string GenerateDataElementsCode()
{
    CodeNamespace globalNamespace = new CodeNamespace();
    globalNamespace.Imports.Add( new CodeNamespaceImport( string.Format( "{0}.DataElements", _project.Target.RootNamespace ) ) );

    CodeCompileUnit unit = new CodeCompileUnit();

    unit.Namespaces.Add( globalNamespace );

    CodeNamespace ns = new CodeNamespace( string.Format( "{0}.DataElements", _project.Target.RootNamespace ) );

    var codesDoc = XDocument.Load( string.Format( @"{0}\{1}", _project.Source.RootPath, _project.Source.UNCL ) );

    var doc = XDocument.Load( string.Format( @"{0}\{1}", _project.Source.RootPath, _project.Source.EDED ) );
    foreach ( XNode node in doc.Descendants( "DataElement" ) )
    {
        CodeTypeDeclaration dataElementClass = new CodeTypeDeclaration()
        {
            Name = string.Format( "E{0}", node.XPathSelectElement( "Identifier" ).Value ),
            IsClass = true
        };

        dataElementClass.Comments.Add( new CodeCommentStatement( node.XPathSelectElement( "Description" ).Value, true ) );

        dataElementClass.BaseTypes.Add( "SimpleObject" );

        CodeAttributeDeclaration dataElementAttr = new CodeAttributeDeclaration( "DataElement" );
        dataElementAttr.Arguments.Add(
            new CodeAttributeArgument
                    {
                        Name = "",
                        Value = new CodePrimitiveExpression( node.XPathSelectElement( "Identifier" ).Value )
                    } );
        dataElementAttr.Arguments.Add(
            new CodeAttributeArgument
            {
                Name = "",
                Value = new CodePrimitiveExpression( node.XPathSelectElement( "Name" ).Value )
            } );
        dataElementAttr.Arguments.Add(
            new CodeAttributeArgument
            {
                Name = "",
                Value = new CodePrimitiveExpression( node.XPathSelectElement( "Description" ).Value )
            } );

        CodeAttributeDeclaration dataElementFormatAttr = new CodeAttributeDeclaration( "DataElementFormat" );
        dataElementFormatAttr.Arguments.Add(
            new CodeAttributeArgument
            {
                Name = "Cardinality",
                Value = new CodePrimitiveExpression( node.XPathSelectElement( "Cardinality" ).Value )
            } );

        dataElementClass.CustomAttributes.Add( dataElementAttr );
        dataElementClass.CustomAttributes.Add( dataElementFormatAttr );

        var codes = codesDoc.XPathSelectElements( "SimpleDataElements/SimpleDataElement/CodeLists/CodeList" ).Where( a => a.XPathSelectElement( "../../Code" ).Value == node.XPathSelectElement( "Identifier" ).Value );

        if ( codes.Count() > 0 )
        {

            CodeTypeDeclaration codesClass = new CodeTypeDeclaration( "Codes" );
            codesClass.Attributes = MemberAttributes.Static;
            codesClass.IsClass = true;

            foreach ( XNode codeNode in codes )
            {
                CodeMemberField con = new CodeMemberField( typeof( string ), string.Format( "Code{0}", codeNode.XPathSelectElement( "Code" ).Value ) );
                con.Attributes = MemberAttributes.Public | MemberAttributes.Const;
                con.InitExpression = new CodePrimitiveExpression( codeNode.XPathSelectElement( "Code" ).Value );

                con.Comments.Add( new CodeCommentStatement( codeNode.XPathSelectElement( "Description" ).Value, true ) );

                codesClass.Members.Add( con );
            }

            dataElementClass.Members.Add( codesClass );

        }

        ns.Types.Add( dataElementClass );

    }

    unit.Namespaces.Add( ns );

    var provider = new Microsoft.CSharp.CSharpCodeProvider();

    using ( var sourceCode = new StringWriter() )
    using ( var indentedTextWriter = new IndentedTextWriter( sourceCode, "    " ) )
    {
        // Generate source code using the code provider.
        provider.GenerateCodeFromCompileUnit( unit,
            indentedTextWriter,
            new CodeGeneratorOptions() { BracingStyle = "C" } );

        return sourceCode.GetStringBuilder().ToString();
    }
}

person Intrepid    schedule 04.07.2014    source источник
comment
Рассматривали ли вы использование просто CSharpCodeProvider, как в моем ответе. Это позволит вам создать строку с желаемым исходным кодом C # и просто динамически ее компилировать.   -  person Konrad Kokosa    schedule 04.07.2014
comment
@KonradKokosa Да, сделал, но это стало слишком запутанным, поскольку я вводил значения из XML-документов, и это становилось беспорядочным и трудным для понимания.   -  person Intrepid    schedule 04.07.2014


Ответы (3)


Когда кто-то говорит CodeDOM, я вижу это так:

// create compiler
CodeDomProvider provider = CSharpCodeProvider.CreateProvider("C#");
CompilerParameters options = new CompilerParameters();
// add all loaded assemblies
options.ReferencedAssemblies.AddRange(
    AppDomain.CurrentDomain.GetAssemblies().Where(item => !item.IsDynamic).Select(item => item.Location).ToArray());
options.GenerateExecutable = false;
options.GenerateInMemory = true;
// source
string source = "using System;namespace Test{public class Test{";
source += "[Conditional()]public E3477 E3477 { get; private set; }";
source += ...
// compile
CompilerResults result = provider.CompileAssemblyFromSource(options, source);
person Sinatr    schedule 04.07.2014
comment
В настоящее время я добавляю исходный код аналогично тому, как вы показали, но я генерирую классы, используя объекты CodeDOM, а затем конвертирую код в строку, прежде чем передать ее компилятору. Я попытался сделать то же самое, что и вы, но получилось слишком запутаться со значениями, вводимыми в строки, что усложняло окончательный код. - person Intrepid; 04.07.2014
comment
@MikeClarke, генерирующий классы с помощью CodeDOM, а затем конвертирующий код в строку перед передачей компилятору ?! Звучит слишком сложно ... - person Konrad Kokosa; 04.07.2014
comment
@KonradKokosa Я использовал туториал, чтобы сделать это; вот почему я пошел по этому пути. - person Intrepid; 04.07.2014
comment
Исходный код string более читабелен, чем ваш код. Если он становится слишком беспорядочным (много string.Format?), Просто организуйте его, например, с использованием методов расширения, которые могут изменить данную строку как шаблон и дать желаемый результат. Было бы неплохо увидеть пример final code hard to follow, может, проще сделать проще. - person Sinatr; 04.07.2014
comment
@Sinatr Я изменил вопрос, включив в него трудный для чтения код. - person Intrepid; 04.07.2014
comment
Вам нужен красивый исходный код (сгенерированный GenerateCodeFromCompileUnit) или скомпилированная сборка? - person Sinatr; 04.07.2014
comment
@Sinatr Я хочу и то, и другое, потому что хочу увидеть сгенерированный окончательный код. Для релизной сборки я не буду использовать GenerateCodeFromCompileUnit(). - person Intrepid; 04.07.2014
comment
Поправьте меня, если я ошибаюсь в том, что вы делаете: прочитайте существующий исходный код, проанализируйте xml с правилами, сгенерируйте Codes класс, добавьте его в исходный код один раз (один раз?), Сохраните его. - person Sinatr; 04.07.2014

Как отмечали другие, частные сеттеры изначально не поддерживаются CodeDom (IIRC, потому что это не относится ко всем языкам, обслуживаемым CodeDom).

Большинство решений рекомендуют явно жестко закодировать "{get; private set;}" в выражение фрагмента, но это не очень помогает, если вам нужны средства доступа с логикой. Это связанный, но другой подход, который позволил мне решить аналогичную проблему. Это некрасиво, но помогает:

Учитывая некоторый вспомогательный класс по линиям

public static class SnippetGenerator
{
    private static CodeDomProvider codeGenProvider = CodeDomProvider.CreateProvider("CSharp");
    private static CodeGeneratorOptions codeGenOptions = new CodeGeneratorOptions() { BracingStyle = "C", BlankLinesBetweenMembers = false };
    private static StringWriter stringWriter = new StringWriter();

    public static string GenerateSnippet(CodeTypeMember member)
    {
        codeGenProvider.GenerateCodeFromMember(member, stringWriter, codeGenOptions);
        string snippet = stringWriter.ToString();
        stringWriter.GetStringBuilder().Length = 0;
        return snippet;
    }
}

Затем вы можете преобразовать существующие свойства CodeDom в фрагменты, которые можно будет обработать пост-обработкой с помощью строковых манипуляций:

CodeMemberProperty componentProperty = new CodeMemberProperty();
... //(build out your property)

// inject private scope onto setter
string propertySnippet = SnippetGenerator.GenerateSnippet(componentProperty);
propertySnippet = propertySnippet.Replace("  set\r\n", "  private set\r\n");
CodeSnippetTypeMember propertySnippetMember = new CodeSnippetTypeMember(propertySnippet);

Примечание. Вероятно, есть лучший способ обрезать набор, чем включение "\ r \ n" (который, возможно, может отличаться в зависимости от настроек вашего поколения / платформы), но это был простой способ убедиться, что он не захватывает неверные подстроки. Выберите способ, наиболее подходящий для вашего проекта.

person Leoul    schedule 10.06.2020

Что ж, если вы хотите использовать объекты Codedom, вы можете создать поле codemember и добавить к его имени геттер / сеттер.

CodeMemberfield field = new CodeMemberField
  {
      Name = "YourPropertyName",
      Attributes = MemberAttributes.Public | MemberAttributes.Final,
      Type = new CodeTypeReference(typeof(YourClassName)),
  };

field.Name += " { get; private set; }";

См. https://stackoverflow.com/a/13679314/3496840

person Shadow3097    schedule 04.07.2014