views:

305

answers:

8

Lets say you have a Class with 300 properties with no backing variables, each of those properties returns a decimal/double.

Example:

public decimal MathValue { get; set; }

Now you decided that each one of those values should be rounded.

I am looking for the simplest way to refactor this without having to rewrite all of those properties.

Something, of this equivalent that actually works :D:

public decimal MathValue { get {return Math.Round(MathValue);} set; }
+6  A: 

No. If you need any custom logic in either getter or setter, you cannot use auto-properties.

Pavel Minaev
And there is absolutely no way of overriding this logic or solution around this without declaring a variable for each one?
Andrew
An alternative would be to write a command line util (or preferably use T4) to generate your 300 properties for you from some other file/data.
consultutah
You could use something like PostSharp to rewrite your properties post-compile. Probably isn't worth it for something as trivial as that, however.
Pavel Minaev
I was thinking of this as more of a theoretical question then a brute force one... I am open to any ideas that could help me work around the problem, with minimal code.
Andrew
I don't see how this answers his question.
ChaosPandion
+5  A: 

You could create a new value type that pretends to be a decimal, but returns the rounded value. Something like this:

struct RoundedDecimal
{
    public decimal Value { get; private set; }

    public RoundedDecimal(decimal value) : this()
    {
        this.Value = value;
    }

    public static implicit operator decimal(RoundedDecimal d)
    {
        return Math.Round(d.Value);
    }
}

Each property in your class should be of type RoundedDecimal instead of decimal.

Andy
I'd call this `Int96`, because that's what it effectively is.
Pavel Minaev
This is a great way to pull it off without breaking the interface.
ChaosPandion
Actually it would probably be better if you made a public readonly field called Value and round it in the constructor.
ChaosPandion
I am liking this solution the most so far. :)
Andrew
This would be so easy to implement as well. ctrl-h "decimal" "RoundedDecimal"
ChaosPandion
@ChaosPandion that would work, too. I was thinking with my approach that this would allow the user of the class to get either the original or rounded values, if necessary. Your approach might be more in line with the name of the class, though.
Andy
Are you getting a this. object can not be instantiated before all of its fields are assigned?
Andrew
I didn't compile my code yesterday, but I did today and got the error. I fixed it by calling the default parameterless constructor.I'll update the code in my answer.
Andy
A: 

But what happens if the client doesn't want a rounded value? ie, some new client code sets a decimal and expects that "exact" value back?

If some clients really need the output of the property call to be rounded, then the client should handle this and leave your class alone.

Leonard H Martin
Well then in that case the get can just be left alone :) for that particular property... the only thing I want to do is avoid declaring a variable for each property.
Andrew
+1  A: 

You could create a derivative of this class that overrides the gets and returns the rounded values. You would then need to modify the base property to be virtual. But that would allow you to define the get without defining the set and using auto properties.

public class Base
{
    public virtual decimal MathValue { get; set; }
}

public class Derived : Base
{
    public override decimal MathValue
    {
        get { return Math.Round(base.MathValue); }
    }
}
gWiz
How could this be accomplished? I would like to override the gets themselves, if that is possible. Could you maybe show me an example?
Andrew
Sure. I also clarified that the whole property needs to be virtual, not just the getter.
gWiz
Inheritance seems like overkill for this.
ChaosPandion
With strict regard to the question "is it possible to get around defining get without defining set", I disagree. With broader regard to his overall scenario, this approach also gives him a rounded and un-rounded version of the class. The resulting class names can then have better semantics. However, I agree that unless he can reap these particular benefits, this might be overkill.
gWiz
A: 

Visual Studio used to have a built-in "prop" code snippet that would generate something like the following code:

    private decimal _MathValue;

    public decimal MathValue
    {
        get { return _MathValue; }
        set { _MathValue = value; }
    }

That would get you most of the way to a complete solution, but since VS 2008 it now generates the automatic property version:

    public decimal MathValue { get; set; }

I haven't tried this yet, but here is a suggestion for creating your own code snippet to get the VS 2005 version of the "prop" code snippet back:

Dr. Wily's Apprentice
I am more interested in a way around the code rather then to manually refactor everything in the class even if I have a snippet that does it for me.
Andrew
Ah, yes I should have read your question more closely. I see that you are in a situation where you already have hundreds of properties defined, so this doesn't really help you all that much.
Dr. Wily's Apprentice
:) thanks for helping though
Andrew
A: 

One option is to use aspect-oriented programming to intercept the property invocations on return and round the return value before passing control back to the caller.

Jason
+3  A: 

Easiest way to refactor the code? Here's what I would do:

  1. Open Notepad++ (get it if you don't have it)
  2. Copy/paste all properties of the class into a blank text area.
  3. Place the cursor at the start of the first line: public decimal MathValue1 { get; set; }
  4. Start recording a macro (click the record button on the toolbar)
  5. hold down ctrl+right arrow (called "word right") 3 times to put the cursor at the beginning of the property name.
  6. do shift+ctrl+right arrow 1 time and do a Copy to put the name of the property in the clipboard
  7. word right 3 more times to put the cursor after the "get"
  8. delete the semi-colon after the get and start typing " { return Math.Round(_"
  9. do a Paste 10 type "); }"
  10. word right 2 more times to put the cursor after the "set"
  11. delete the semi-colon after the set and start typing " { _"
  12. do a Paste
  13. type " = value; }
  14. press the End key to get the cursor to the end of the line
  15. press the right arrow key to get the cursor to the beginning of the next line.
  16. press the stop button to end your macro (square button on the toolbar)
  17. Click the "Run a macro multiple times" button (a double-arrow icon on the toolbar) and say "Run until the end of file"
  18. Copy/paste the resulting text back into your class to replace the original property definitions.

Now you'll need to define a set of corresponding private variables that begin with an underscore but otherwise have the same names as the properties. Start with a fresh copy of the properties from your class and perform a similar set of steps as described above.

My assumption is that each line starts with 2 tabs and there are no empty lines between properties.

Rather than having each property call Math.Round, you may want to consider defining your own utility function that they all call so that if you need to change it again, you can just change it in one place.

Dr. Wily's Apprentice
+1 - Never would have though of this. He may want to hit ctrl-k-d before he does it so VS will clean up all the white space.
ChaosPandion
A: 

You could do this with PostSharp or some other .NET-based AOP framework. Here is the MethodExecutionEventArgs.ReturnValue property that says it can be used to "modify the return value..."

This will do it:

[Serializable]
public class RoundingAttribute : OnMethodBoundaryAspect
{
 public override void OnExit(MethodExecutionEventArgs eventArgs)
 {
  base.OnExit(eventArgs);
  eventArgs.ReturnValue = Math.Round((double)eventArgs.ReturnValue, 2);
 }
}

class MyClass
{
 public double NotRounded { get; set; }

 public double Rounded { [Rounding] get; set; }
}

class Program
{
 static void Main(string[] args)
 {
  var c = new MyClass
          {
           Rounded = 1.99999, 
     NotRounded = 1.99999
          };

  Console.WriteLine("Rounded = {0}", c.Rounded); // Writes 2
  Console.WriteLine("Not Rounded = {0}", c.NotRounded);  // Writes 1.99999
 }
}
consultutah