开发者

C# update a varying property on each item within a collection

I have this code (which is way simplified from the real code):

public interface IAmount
{
  decimal Amount { get; set; }
}

public class SomeAmount : IAmount
{
  public decimal Amount { get; set; }
}

public static void Up开发者_StackOverflow中文版dateAmounts( this IEnumerable< IAmount > amounts, decimal totalAmount )
{
  foreach ( IAmount amount in amounts )
    amount.Amount = GetAmount();      
}

public static decimal GetAmount()
{
  return 12345m;
}

The code works great and the UpdateAmounts ExtensionMethod is used quite frequently throughout the application to apply a penny rounding routine (not like the one in Office Space!)

The problem is I do not like having an IAmount interface with a specific name of the column I need to set (Amount). In a new requirement, I need to update a database entity collection with this routine and the name of the property I need to update is "GrossAmount". Sometimes too it would be nice to update other writable decimal properties in a similar manner.

The problem is that it appears I cannot simple say amount.Field = GetAmount() where the .Field part deals with a different property on the entity. Is it possible somehow? I am not on C# 4.0, so using a dynamic type isn't possible for me yet.


You could do this in a more functional style, something like this:

    public class Something
    {
        public decimal Amount { get; set; }
        public decimal OtherAmount { get; set; }
    }

    public static void UpdateAmounts<T, U>(IEnumerable<T> items, Action<T,U> setter, Func<T, U> getter)
    {
        foreach (var o in items)
        {
            setter(o, getter(o));
        }
    }

    public void QuickTest()
    {
        var s = new [] { new Something() { Amount = 1, OtherAmount = 11 }, new Something() { Amount = 2, OtherAmount = 22 }};
        UpdateAmounts(s, (o,v) => o.Amount = v, (o) => o.Amount + 1);
        UpdateAmounts(s, (o,v) => o.OtherAmount = v, (o) => o.OtherAmount + 2);
    }


What about having a Dictionary-like interface ?

public interface IAmount {

        decimal this[string fieldName] { get; set; }
}

Implementation is simply:

public class Money : IAmout {
    private Dictionary<string, decimal> _dict;

    public decimal this[string fieldName] {
        get { return _dict[fieldName]; }
        set { _dict[fieldName] = value; }
    }
}

(of course, it requires some error checking)

Then, one can write:

Money m = new Money();
m["Amount"] = ...

or

m["GrossAmount"] = ...

Not as nice as dynamic, I agree.


public class SomeAmount : IAmount
{
  decimal amount;
  public decimal Amount 
  { 
       get{return this.amount;}
       set{this.amount=value; }
  }
}


Not sure how willing you are to screw with your entities, but...

public class SomeGrossAmount : IAmount
{
    public decimal GrossAmount { get; set; }
    decimal IAmount.Amount
    {
        get { return GrossAmount; }
        set { GrossAmount = value; }
    }   
}

This hides the Amount implementation of your entity in any context that it's not directly used as an IAmount, while still allowing it to function as an IAmount.


You could hide the Field property, like this:

public interface IAmount
{
    decimal Field
    { get; set; }
}

public class SomeAmount : IAmount
{
    public decimal Amount
    { get; set; }

    decimal IAmount.Field
    {
        get { return Amount; }
        set { Amount = value; }
    }
}

public class SomeGrossAmount : IAmount
{
    public decimal GrossAmount
    { get; set; }

    decimal IAmount.Field
    {
        get { return GrossAmount; }
        set { GrossAmount= value; }
    }
}

Casting the object to IAmount reveals the Field for your purposes. Otherwise, Field is hidden in the designer and Amount (or GrossAmount) is what you'll be working with.


You could also use reflection in order to apply your rounding on every decimal inside your type.

public static void UpdateAmounts( this IEnumerable< IAmount > amounts, decimal totalAmount )
{
  foreach ( IAmount amount in amounts ) 
  {
     var myType = amount.GetType();
     var myTypeProperties = myType.GetProperties();
     foreach (PropertyInfo h_pi in myTypeProperties) 
     {
        if (h_pi.Property_Type == typeof(decimal)) // or h_pi.Name == "Amount" || h_pi.Name                                      == "GrossAmount"...
        {
           //DoStuff
        }
     }
  }
    amount.Amount = GetAmount();      
}

there is better way to write that but I'm sure you get the point. Using reflection you could also get rid of the whole interface thing and simply go by reflection.

P.S. : Reflection is not the fastest way to go but it's an easy way to get runtime flexibility.

Let me know if that's what you wanted...


Or, when you do not mind using reflection (it is a bit slower): it is very powerful in combination with attributes. First, create an attribute used to mark the decimal property you need:

[AttributeUsage(AttributeTargets.Property,
    Inherited = true, AllowMultiple = false)]
sealed class DecimalFieldAttribute : Attribute
{
    public DecimalFieldAttribute()
    { }
}

Mark your field with the attribute, e.g.:

public class SomeGrossAmount
{
    [DecimalField]
    public decimal GrossAmount
    {
        get;
        set;
    }
}

Then use this method to set such a field:

public static void SetDecimalField(object obj, decimal value)
{
    // Enumerate through all the properties to find one marked
    // with the DecimalFieldAttribute.
    PropertyInfo[] properties = obj.GetType().GetProperties();
    PropertyInfo decimalfieldproperty = null;
    foreach (PropertyInfo property in properties)
    {
        object[] attributes = property.GetCustomAttributes(typeof(DecimalFieldAttribute), true);
        if (attributes.Length == 0)
            continue;
        // Check, or just break; when you'll not be making this error.
        if (decimalfieldproperty != null)
            throw new Exception("More than one property is marked with the DecimalFieldAttribute.");

        // Found a candidate.
        decimalfieldproperty = property;
    }
    // Check, or just assume that you'll not be making this error.
    if (decimalfieldproperty == null)
        throw new Exception("No property with the DecimalFieldAttribute found.");

    // Set the value.
    decimalfieldproperty.SetValue(obj, value, null);
}


I would suggest something like this:

    public class Entity
    {
        public decimal Amount { get; set; }
        public decimal OtherAmount { get; set; }
    }

    public static void Update<TEntity, TValue>(this IEnumerable<TEntity> entities, Func<TValue> valueGetter, Action<TEntity, TValue> valueSetter)
    {
        foreach (TEntity entity in entities)
        {
            TValue value = valueGetter.Invoke();
            valueSetter.Invoke(entity, value);
        }
    }

    public static decimal GetAmount()
    {
        throw new NotImplementedException();
    }

    public static decimal GetOtherAmount()
    {
        throw new NotImplementedException();
    }

    public static IEnumerable<Entity> GetEntities()
    {
        throw new NotImplementedException();
    }

    static void Main()
    {
        IEnumerable<Entity> entities = GetEntities();

        entities.Update<Entity, decimal>(GetAmount, (entity, value) => entity.Amount = value);
        entities.Update<Entity, decimal>(GetOtherAmount, (entity, otherValue) => entity.OtherAmount = otherValue);
    }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜