20

I have the following structure and would like a solution with both benefits from the following two classes. The first class is using strings and strongly typed members:

public class UserSessionData
{
    private string Get(string key)
    {
        throw new NotImplementedException("TODO: Get from external source");
    }
    private void Set(string key, string value)
    {
        throw new NotImplementedException("TODO: Set in external source");
    }

    public string CustomerNumber {
        get { return Get("CustomerNumber"); }
        set { Set("CustomerNumber", value); }
    }
    public string FirstName {
        get { return Get("FirstName"); }
        set { Set("FirstName", value); }
    }
    public string LastName {
        get { return Get("LastName"); }
        set { Set("LastName", value); }
    }

    // ... a couple of hundreds of these
}

I can imagine an alternative being a Get and Set method with an enum parameter. Here is the second class:

public class UserSessionData
{
    public enum What {
        CustomerNumber, FirstName, LastName, // ...
    }

    public string Get (What what) { return MyExternalSource(what); }
    public string Set (What what, string value) { return MyExternalSource(what); }
}

But the consumer side of class #2 is not pretty:
UserSessionData.Get(UserSessionData.What.CustomerNumber)
Compare it to the first class: UserSessionData.CustomerNumber

Is there a strongly typed way of calling the Get and Set methods in my first class example? Stated another way: How do I get the benefits from both classes, i.e. the maintainability of strongly typed members and a nice-looking syntax?

Simeon
  • 5,519
  • 3
  • 29
  • 51
  • `Get(CustomerNumber.GetType().Name);`: This would not be equivalent to `Get("CustomerNumber");`. Instead, it would be equivalent to `Get("String");` – Daniel Hilgarth Jul 09 '13 at 14:29
  • I'm not sure that "strongly typed" is the right description of what you are doing here. Even if you were forcing people to call Get directly the type is still known and well defined. Not quite sure what you would call it though... – Chris Jul 09 '13 at 14:29
  • 3
    what is a point in using public *non static* property in static class ? – Tigran Jul 09 '13 at 14:32
  • 1
    You could generate the member properties with design-time [T4 Templates](http://msdn.microsoft.com/en-us/library/vstudio/bb126445.aspx). – Alex Jul 09 '13 at 14:32
  • @Alex or with a snippet ;) – Gusdor Jul 09 '13 at 14:35
  • Can I ask what the endgame is here? If this is a database/webservice, is it worth using an ORM tool to generate strong mappings for your data store? If this is serialized data (a file perhaps) consider de serializing once. You may even consider remote objects. – Gusdor Jul 09 '13 at 14:37
  • 2
    @Tigran: There is no point. It simply won't compile. – Daniel Hilgarth Jul 09 '13 at 14:38
  • *"there is no dynamic way to call Get and Set"* - actually there is a dynamic way, but it's not strongly typed (as all dynamic things are) – Cristian Lupascu Jul 09 '13 at 14:44
  • 1
    @DanielHilgarth: compilation apart, I have a difficulty to understand requirement. – Tigran Jul 09 '13 at 14:47
  • @DanielHilgarth: You are correct regarding the type name - I've removed that from my question now. Maintainability is however now crippled – Simeon Jul 09 '13 at 22:05
  • @Tigran: Valid point. I've edited my question – Simeon Jul 09 '13 at 22:06
  • @w0lf: I've clarified this a bit in my question – Simeon Jul 09 '13 at 22:06
  • +1 for caring for and improving your question. – Daniel Hilgarth Jul 10 '13 at 07:45

6 Answers6

34

.Net 4.5 or newer

If you use .Net 4.5 or newer you can make use of CallerMemberNameAttribute so you can call it like this:

public string CustomerNumber {
    get { return Get(); }
}

To make this work, modify the Get method by adding the attribute to the parameter:

private string Get([CallerMemberName] string key)
{
    ...
}

Performance notice: The compiler will insert a string as parameter at the calling site, so this is fast.


.Net 4.0 or earlier

If you use .Net 4.0 or earlier, you can still use strongly typed property names instead of typing strings manually, but you need to implement a method like this to extract the property name from an Expression, and then you can call it using an expression:

public string CustomerNumber {
    get { return Get(() => this.CustomerNumber ); }
}

The setters can be implemented in the same fashion.

Performance notice: The string is extracted at runtime, so this is slower than using CallerMemberNameAttribute.

Community
  • 1
  • 1
lightbricko
  • 2,649
  • 15
  • 21
  • 1
    +1. I use this method for all my OnNotifyPropertyChanged calls in MVVM apps. It performs well due to the precompiled expression tree. – Gusdor Jul 09 '13 at 14:39
  • Alternatively, each property body can contain this fixed code: `get { return Get(System.Reflection.MethodBase.GetCurrentMethod().Name.Substring(4)); } set { Set(System.Reflection.MethodBase.GetCurrentMethod().Name.Substring(4), value); }`. – GSerg Jul 09 '13 at 14:40
  • +1, didn't know about `CallerMemberNameAttribute`, that's really cool! – Blindy Jul 09 '13 at 14:51
  • Isn't GetCurrentMethod much slower than just passing a string? IIRC most ORM frameworks switched to the string/expression way. – quetzalcoatl Jul 09 '13 at 14:52
  • 2
    @Gusdor Expression trees are not precompiled, they're constructed every time. Comparatively very expensive. Even if you get rid of external dependencies (like `this` in lightbricko's examples). – Cory Nelson Jul 09 '13 at 15:56
  • The `CallerMemberNameAttribute` looks really neat! And I think that both your C# 4 and C# 5 solution solves my problem. Well done sir, and thank you very much! – Simeon Jul 09 '13 at 22:15
  • @CoryNelson is the construction not cached, similarly to lambda expressions? – Gusdor Jul 10 '13 at 08:42
  • In .Net 4 or older, one could also examine the stack to grab the previous frame's method name. new StackFrame(1).GetMethod().Name. Is the performance of this likely to be seriously ropey? – Gusdor Jul 22 '13 at 14:19
  • @Gusdor the StackFrame approach is **unreliable** due to compiler optimization. Check the comment by Abel at http://stackoverflow.com/a/44215/1105687 – lightbricko Jul 23 '13 at 13:05
  • @lightbricko thanks for clearing that up. How annoying! Trying to find a .net 4 approach that doesn't tie me to resharper or Expression trees. – Gusdor Jul 23 '13 at 14:25
8

You can use a T4 template to generate the class. In the T4 template you just name all the properties - you could get them through reflection from an enum, too, but using a string array is simpler.

Add New Item -> Text template

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<# var properties = new string [] {
      "CustomerNumber", // This is where you define the properties
      "FirstName",
      "LastName"
}; #>
class UserSessionData {
<#  
  foreach (string propertyName in properties) 
  { #>
  public string <#= propertyName #>{
    get { return Get("<#= propertyName #>"); }
    set { Set("<#= propertyName #>", value); }
  }
<# } #>
}

More info @ Design-Time Code Generation by using T4 Text Templates

Alex
  • 7,728
  • 3
  • 35
  • 62
  • The problem is not generating the wall of code. The problem is maintaining it. Maintaining names of properties provided as literal strings in code is hard. – GSerg Jul 09 '13 at 14:44
  • 2
    @GSerg They're only listed once, in the template in this case. If you need to change the class, you just edit the template. Also, like I said, you can use an enum, if you must. – Alex Jul 09 '13 at 14:46
  • @GSerg, I'm sorry, I'm not following. You have to write them at least once somewhere, and in this case they're written in an easily maintainable list on top. Personally this is the way I'd (and *did*) go, so +1 – Blindy Jul 09 '13 at 14:47
  • @Blindy A scenario: You generate this class with a template, then next week another dev in the company feels like refactoring and changes some property names here and there, including some of your class. The IDE properly updates the changed property names all over the codebase, but the literal strings inside the properties do not change. Your code is now wrong. – GSerg Jul 09 '13 at 14:52
  • 2
    Yeah, but wrong in a way that won't compile. This isn't run-time binding, this is still getting properly compiled, with full Intellisense support in the mean time. Can't really ask for much more (that magic attribute aside if you're coding for 4.5). – Blindy Jul 09 '13 at 14:53
  • I do think that this is a good suggestion, and the accepted answer was extremely spot-on and therefore hard to compete with. Thanks for your efforts, I have learned about T4 templates thanks to this answer! – Simeon Jul 09 '13 at 22:23
1

How about an enum with some extension methods, like this:

// usage:
[TestMethod]
public void example()
{
    UserSessionData.CustomerNumber.Set("cust num");
    Assert.AreEqual("cust num", UserSessionData.CustomerNumber.Get());
}

// implementation:
public enum UserSessionData
{
    CustomerNumber,
    FirstName,
}

public static class UserSessionDataHelper
{
    private static Dictionary<string, string> values = new Dictionary<string, string>();

    private static string GetName(this UserSessionData field)
    {
        return Enum.GetName(typeof(UserSessionData), field);
    }

    public static string Get(this UserSessionData field)
    {
        return values[field.GetName()];
    }

    public static void Set(this UserSessionData field, string value)
    {
        values[field.GetName()] = value;
    }
}
default.kramer
  • 5,943
  • 2
  • 32
  • 50
  • Thanks for your answer, I salute your efforts! On the finish line, I have to accept the answer from @lightbricko since its syntax shortens the call by `".Get()".Count` characters... – Simeon Jul 09 '13 at 22:28
0

This pattern allows loosely coupled resource keys. A hashmap that excepts any key type, for the purpose of extensibility. You often see this with system messages in IoC containers.

In my experience, this pattern grants great flexibility with the cost of required documentation. Consider solving the problem with convention rather than implementation. I try to stick to enumerated values where possible.

  • They are immutable primitives with strong names.
  • They can be groupedinto their parent type.
  • They can be iterated quite simply if required.
  • They respond to refactoring.
Gusdor
  • 14,001
  • 2
  • 52
  • 64
  • Hi, I don't get how this is answering my question. Which pattern are you referring to as "this pattern"? Are you suggesting that I use the `enum` variant that I included in my question? Some code would have helped explaining what you are advocating here – Simeon Jul 10 '13 at 07:31
  • What I am advocating is convention over code. Dont complicate your codebase with a strongly typed system if you can help it. The accepted answer is very good. I suggest better defining 'cleaner' in the question... – Gusdor Jul 10 '13 at 08:41
  • Okay, would that be to use the second class in my question? I already have that solution, but the syntax is quite cluttery. I've improved my question further, defining "cleaner" and expanded on the key issue – Simeon Jul 10 '13 at 11:45
0

If you are using .NET 4.0 or later you can use DynamicObject and override its TryGetMember and TrySetMember methods to do this fully dynamic. This won't be strongly typed though.

MrDosu
  • 3,427
  • 15
  • 18
  • 1
    That's not type safe unfortunately. It was my first instinct too however. – Blindy Jul 09 '13 at 14:49
  • That's true but as clear from the given answers so far Simeon will have to live with some sort of drawback to his ideal situation, be it more hassle with the actual call or with type conversion. – MrDosu Jul 09 '13 at 14:53
  • Thanks for your effort and suggestion! On a sidenote, I can't find any drawbacks in the accepted answer (which is great of course!) – Simeon Jul 09 '13 at 22:18
  • Personally i would avoid a couple of hundred pure boilerplate property definitions (drawback) at all costs, unless there is a very specific reason to do so (e.g. the class needs to meet certain expectations regarding reflection). It seems like the problem at hand could be solved more elegantly using another approach. – MrDosu Jul 10 '13 at 10:53
0

I don't know why such a simple soultion has not yet been proposed:

public class UserSessionData
{
    private string Get(What what)
    {
        throw new NotImplementedException("TODO: Get from external source");
    }
    private void Set(What what, string value)
    {
        throw new NotImplementedException("TODO: Set in external source");
    }

    public string CustomerNumber {
        get { return Get(What.CustomerNumber); }
        set { Set(What.CustomerNumber, value); }
    }

    // ... 
}

public enum What
{
    CustomerNumber, FirstName, LastName, // ...
}

And the usage you so liked: userSessionData.CustomerNumber

If your "external source" prefer strings, you can make What enum private and cast enum to string.

astef
  • 8,575
  • 4
  • 56
  • 95
  • It hasn't been proposed because you'd have to maintain both the `enum` and the properties in the class, and make sure they match. – Alex Dec 04 '15 at 11:43