5

I have a class that's something like this:

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
   }
   ...
}

(Yeah, I know StackTrace/reflection is slow; don't flame me bro)

Given that ALL the properties are declared identically, what I'd LOVE to be able to do is have some simple/clean way to declare them w/o needing to dup the same get/set code over and over.
I need Intellisense on all properties, which precludes the use of eg. ExpandoObject.

If I were in C/C++ land, I could use a macro, eg:

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

then:

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

but of course this is C#.

So... any clever ideas?

#### EDIT ###
I guess my original post wasn't sufficiently clear.
My helper function _getValue() does some customised lookup & processing based on which Property is being called. It doesn't just store/retrieve a simple prop-specific value.
If all I needed were simple values, then I'd just use Automatic Properties

public string { get; set; }

and be done with it, and wouldn't have asked this question.

dlchambers
  • 3,511
  • 3
  • 29
  • 34
  • 1
    T4? Visual Studio macros ... so many possibilities ... –  Jun 11 '15 at 19:25
  • Another thing to consider is that if you don't need to do any additional processing.. you can simply do `public string City { get; set; }` – entropic Jun 11 '15 at 19:26
  • 1
    howcome `public string City { get; set; }` doesn't ever compile for me. – maraaaaaaaa Jun 11 '15 at 19:27
  • Use T4, you can probably avoid reflection as well if you use it. – Lucas Trzesniewski Jun 11 '15 at 19:27
  • 1
    Read about `automatic properties` in C#. That's what you are looking after. – Rahul Jun 11 '15 at 19:28
  • I would be careful with using the stack trace. Many issues can crop up when the code is JIT compiled. – ChaosPandion Jun 11 '15 at 19:29
  • [Visual studio code snippets](https://msdn.microsoft.com/en-us/library/ms165394.aspx) are pretty decent for auto-generating code via a template. – ryanyuyu Jun 11 '15 at 19:30
  • what `_getValue()` do? – Nguyen Kien Jun 11 '15 at 19:30
  • entropic and Rahul, please see my edit – dlchambers Jun 11 '15 at 19:36
  • You could probably make something workable using PostSharp and a custom attribute. Have you tried that? – julealgon Jun 11 '15 at 19:38
  • 2
    Please show what kind of lookup/processing you are doing in `getValue` and `setValue`. There may be a solution but to tell you which solution we need to know more about what you are doing ([`CallerMemberName`](https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx) may be what you want, but we need to know more to know for sure, you may want T4 also. If you show us what you are trying to do we can tell you which you need) – Scott Chamberlain Jun 11 '15 at 19:44

4 Answers4

5

First off: CallerMemberNameAttribute does inject the calling member name, so there's no need for reflection:

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 = "")
   {
   }
}

Secondly: The generation of type members can be achieved by leveraging a T4 template for the .cs-file generation:

<#@ 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 = "") {}
    }
}

You can even offload the _setValue and _getValue to include files, to offer reusability for other templates as well.

T4 templates do have the advantage over macros, that the code can be re-generated at any time. So adaptions to your source code (may it be implementation adaptions or property renaming) can be applied even after the initial generation.

ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
  • 1
    @ChaosPandion ah, thanks for adding the types to the parameters - I have clearly missed that ... –  Jun 12 '15 at 20:53
1

Here is a nasty hack using RealProxy and MarshalByRefObject, that will let you intercept property calls and do whatever you want.

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);
        }
    }
}

Then when you use it like this:

var x = Abc.NewInstance();

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

You will see the following in your console window:

Before set_City
After set_City
Before get_State
After get_State
John Koerner
  • 37,428
  • 8
  • 84
  • 134
  • Never knew about the possibility to create a transparent proxy... nice! But I would prefer AOP over reflection any time... –  Jun 11 '15 at 20:51
0

Somebody figured out how to use the c preprocessor in c#

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

However: You should avoid using stack traces for your purpose!!!! Pass a string to _getValue("name") or auto-generate fields. If you use stack-traces, you have to deactivate method inlining (easy) as well as tail call optimization (not sure if this is possible).

Zotta
  • 2,513
  • 1
  • 21
  • 27
  • Quite clever. Just keep in mind that this could be a problem when the affected files are under source control and you are using an automated build process. TFS usually marks the files as readonly and uses that as a reference that the file is not checked out. – julealgon Jun 11 '15 at 19:44
  • That preprocessor post sure drew a lot of strong opinion :) – dlchambers Jun 11 '15 at 19:46
  • I look at it this way: if using a preprocessor prevents you from using stack traces to determine the property name, it is an improvement. – Zotta Jun 11 '15 at 19:50
  • 1
    With the C# 5 compiler `CallerMemberNameAttribute` has been added: https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute.aspx –  Jun 11 '15 at 20:31
0

I made a Visual Studio code snippet that will auto-generate the code for you.

This is the .snippet file contents:

<?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>

Save this XML document as a .snippet file. Then just follow these steps from the MSDN To Add a Code Snippet to Visual Studio

  1. You can add your own snippets to your Visual Studio installation by using the Code Snippets Manager. Open the Code Snippets Manager (Tools/Code Snippets Manager).
  2. Click the Import button.
  3. Go to the location where you saved the code snippet, select it, and click Open.
  4. The Import Code Snippet dialog opens, asking you to choose where to add the snippet from the choices in the right pane. One of the choices should be My Code Snippets. Select it and click Finish, then OK.
  5. The snippet is copied to the following location: %USERPROFILE%\Documents\Visual Studio 2013\Code Snippets\Visual C#\My Code Snippets
  6. In the file click Insert Snippet on the context menu, then My Code Snippets. You should see a snippet named My Visual Basic Code Snippet. Double-click it.

Notes:

After typing whatever is in the <ShortCut> block (currently myprop), just hit Tab to have the Visual Studio text editor insert that for you.

ryanyuyu
  • 6,366
  • 10
  • 48
  • 53