【问题标题】:Is there a way to implement multiple Properties with code reuse in C#?有没有办法在 C# 中通过代码重用来实现多个属性?
【发布时间】:2015-08-27 16:10:27
【问题描述】:

我有一个类似这样的课程:

public class Abc
{
   public string City
   {
      get { return _getValue(); }
      set { _setValue(value); }
   }
   public string State
   {
      get { return _getValue(); }
      set { _setValue(value); }
   }
   private string _getValue()
   {
      // determine return value via StackTrace and reflection
   }
   ...
}

(是的,我知道 StackTrace/reflection 很慢;别惹我兄弟)

鉴于 ALL 属性声明相同,我希望能够有一些简单/干净的方式来声明它们而无需复制相同的 get/set一遍又一遍的代码。
我在所有属性上都需要 Intellisense,这排除了使用例如。 ExpandoObject.

如果我在 C/C++ 领域,我可以使用宏,例如:

#define DEFPROP(name) \
   public string name \
   { \
      get { return _getValue(); } \
      set { _setValue(value); } \
   } \

然后:

public class Abc
{
   DEFPROP(City)
   DEFPROP(State)
   ...
}

当然这是 C#。

那么……有什么聪明的主意吗?

#### 编辑###
我想我原来的帖子不够清楚。
我的辅助函数 _getValue() 会根据调用的属性进行一些自定义的查找和处理。它不只是存储/检索简单的特定于道具的值。
如果我只需要简单的值,那么我就使用自动属性

public string { get; set; }

就这样结束了,就不会问这个问题了。

【问题讨论】:

  • T4? Visual Studio 宏...很多可能性...
  • 另一件要考虑的事情是,如果你不需要做任何额外的处理..你可以简单地做public string City { get; set; }
  • 怎么public string City { get; set; } 从来没有为我编译过。
  • 阅读 C# 中的 automatic properties。这就是你所关心的。
  • 请在getValuesetValue 中显示您正在执行哪种查找/处理。可能有解决方案,但要告诉您哪种解决方案我们需要更多地了解您在做什么(CallerMemberName 可能是您想要的,但我们需要知道更多才能确定,您可能也想要 T4。如果您告诉我们你想要做什么,我们可以告诉你你需要什么)

标签: c# properties


【解决方案1】:

首先:CallerMemberNameAttribute 确实注入了调用成员名称,因此不需要反射:

public class Abc
{
   public string City
   {
      get { return _getValue(); }
      set { _setValue(value); }
   }
   public string State
   {
      get { return _getValue(); }
      set { _setValue(value); }
   }
   private string _getValue([CallerMemberName] string memberName = "")
   {
   }

   private void _setValue(string value,
                          [CallerMemberName] string memberName = "")
   {
   }
}

其次:类型成员的生成可以通过利用 T4 模板生成 .cs 文件来实现:

<#@ output extension=".cs" #>
<#
var properties = new[]
{
    "City",
    "State"
};
#>
using System.Runtime.CompilerServices;

namespace MyNamespace
{
    public class Abc
    {
<# foreach (var property in properties) { #>
        public string <#= property #>
        {
            get { return _getValue(); }
            set { _setValue(value); }
        }
<# } #>
        private string _getValue([CallerMemberName] string memberName = "") {}
        private void _setValue(string value, [CallerMemberName] string memberName = "") {}
    }
}

您甚至可以卸载 _setValue_getValue 以包含文件,从而为其他模板提供可重用性。

T4 模板确实比宏具有优势,即可以随时重新生成代码。因此,即使在初始生成之后,也可以应用对源代码的修改(可能是实现修改或属性重命名)。

【讨论】:

  • @ChaosPandion 啊,感谢您将类型添加到参数中-我显然错过了...
【解决方案2】:

这是一个使用 RealProxy 和 MarshalByRefObject 的令人讨厌的 hack,它可以让你拦截属性调用并做任何你想做的事情。

public class Abc : MarshalByRefObject
{
    public string City { get; set; }
    public string State { get; set; }

    private Abc()
    {
    }

    public static Abc NewInstance()
    {
        var proxy = new AbcProxy(new Abc());
        return (Abc)proxy.GetTransparentProxy();
    }
}

public class AbcProxy : RealProxy
{
    private readonly Abc _realInstace;

    public AbcProxy(Abc instance) : base(typeof (Abc))
    {
        _realInstace = instance;
    }

    public override System.Runtime.Remoting.Messaging.IMessage Invoke(System.Runtime.Remoting.Messaging.IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;
        var methodInfo = methodCall.MethodBase as MethodInfo;
        Console.WriteLine("Before " + methodInfo.Name);
        try
        {
            var result = methodInfo.Invoke(_realInstace, methodCall.InArgs);
            Console.WriteLine("After " + methodInfo.Name);
            return new ReturnMessage(result, null, 0,
             methodCall.LogicalCallContext, methodCall);
        }
        catch (Exception e)
        {
            return new ReturnMessage(e, methodCall);
        }
    }
}

那么当你这样使用它时:

var x = Abc.NewInstance();

x.City = "hi";
var y = x.State;

您将在控制台窗口中看到以下内容:

Before set_City
After set_City
Before get_State
After get_State

【讨论】:

  • 从来不知道创建透明代理的可能性...太好了!但我更喜欢 AOP 而不是反射......
【解决方案3】:

有人想出了如何在 c# 中使用 c 预处理器

https://stackoverflow.com/a/15703757/417577

但是:您应该避免将堆栈跟踪用于您的目的!!!!将字符串传递给 _getValue("name") 或自动生成字段。如果您使用堆栈跟踪,则必须停用方法内联(简单)以及尾调用优化(不确定这是否可能)。

【讨论】:

  • 相当聪明。请记住,当受影响的文件处于源代码控制之下并且您正在使用自动构建过程时,这可能是一个问题。 TFS 通常将文件标记为只读,并将其用作文件未检出的参考。
  • 那篇预处理器帖子确实引起了很多强烈的意见:)
  • 我是这样看的:如果使用预处理器阻止您使用堆栈跟踪来确定属性名称,这是一种改进。
  • 使用 C# 5 编译器 CallerMemberNameAttribute 已添加:msdn.microsoft.com/en-us/library/…
【解决方案4】:

我制作了一个 Visual Studio 代码 sn-p,它将为您自动生成代码

这是.snippet文件内容:

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets
    xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>MyProp</Title>
            <Author>ryanyuyu</Author>
            <Description>Auto get/set property</Description>
            <Shortcut>myprop</Shortcut>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>propName</ID>
                    <Default>MyProperty</Default>
                    <ToolTip>The name of the property.</ToolTip>
                </Literal>
            </Declarations>
            <Code Language="CSharp">
                <![CDATA[
        public string $propName$
        {
            get { return _getValue(); }
            set { _setValue(value); }
        }
            ]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

将此 XML 文档另存为 .snippet 文件。然后只需按照 MSDN To Add a Code Snippet to Visual Studio 中的这些步骤进行操作即可

  1. 您可以使用代码片段管理器将自己的 sn-ps 添加到 Visual Studio 安装中。打开代码片段管理器(工具/代码片段管理器)。
  2. 点击导入按钮。
  3. 转到保存代码 sn-p 的位置,选择它,然后单击“打开”。
  4. “导入代码片段”对话框打开,要求您从右侧窗格的选项中选择添加 sn-p 的位置。选择之一应该是我的代码片段。选择它并单击完成,然后单击确定。
  5. sn-p 被复制到以下位置: %USERPROFILE%\Documents\Visual Studio 2013\Code Snippets\Visual C#\My Code Snippets
  6. 在文件中单击上下文菜单上的插入片段,然后单击我的代码片段。您应该会看到一个名为 My Visual Basic Code Snippet 的 sn-p。双击它。

注意事项:

键入&lt;ShortCut&gt; 块(当前为myprop)中的任何内容后,只需点击Tab 让Visual Studio 文本编辑器为您插入。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 2020-12-05
    • 1970-01-01
    • 2019-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多