0

I have a list of an object, I want to update all the fields that are "null" to string.empty.

here is the code:

public class Class1
{
    public string P1 { get; set; }
    public string P2 { get; set; }
    public string P3 { get; set; }
}

I want to have a code which goes and finda all the null values in all the fields and change the value to string.empty

     static void Main(string[] args)
    {

        var list= new List<Class1>();
        var class1 = new Class1 {P1 = "P1-1", P2 = "P2-1", P3="null"};
        list.Add(class1);
        var class2 = new Class1 { P1 = "P1-2", P2 = "P2-2", P3 = "null" };
        list.Add(class2);

    }

so I would need the class1.P3 and class2.P3 to be found and their value to be replaced.

Thanks

eric_eri
  • 699
  • 11
  • 19

2 Answers2

3

You can write a short generic function like this:

private static IEnumerable<TSource> ReplaceValues<TSource>(IEnumerable<TSource> source, object oldValue,
    object newValue)
{
    var properties = typeof(TSource).GetProperties();
    foreach (var item in source)
    {
        foreach (var propertyInfo in properties.Where(t => Equals(t.GetValue(item), oldValue)))
        {
            propertyInfo.SetValue(item, newValue);
        }
        yield return item;
    }
}

This is more efficient than yours since your collection type is TSource, that means all of the types inside will have the same properties. Obtaining and caching those properties will speed up the process, because you're calling Type.GetProperties() only once and than you're operating and filtering those results.

Update

As discussed in the comment section below with Ivan Stoev, it would be more suitable to have the method just modify the collection without returning any value:

private static void ReplaceValues<TSource>(IEnumerable<TSource> source, object oldValue,
    object newValue)
{
    var properties = typeof(TSource).GetProperties();
    foreach (var item in source)
    {
        foreach (var propertyInfo in properties.Where(t => Equals(t.GetValue(item), oldValue)))
        {
            propertyInfo.SetValue(item, newValue);
        }
    }
}
Deadzone
  • 793
  • 1
  • 11
  • 33
  • You need to compare strings by value. Use `Equals()` – SLaks Jun 26 '17 at 17:45
  • @SLaks good idea, tho that involves boxing/unboxing, passing an `IEqualityComparer` should be even better, I will edit the answer. – Deadzone Jun 26 '17 at 17:46
  • @SLaks what do you think about it now? – Deadzone Jun 26 '17 at 17:48
  • There is no point in using `EqualityComparer`, since there is no sane way to specialize (since you're handling arbitrary property types). Just call `object.Equals(object, object)`. – SLaks Jun 26 '17 at 17:53
  • 1
    Looks fine. For optimal perf, cache the properties across calls in a static class: https://stackoverflow.com/q/686630/34397 – SLaks Jun 26 '17 at 17:55
  • Thanks @Deadzone, – eric_eri Jun 26 '17 at 18:32
  • Hmm, why this is iterator method and not simple `void`? For unknown reason it yields each input item several times and also due to the deferred execution calling `myList. ReplaceValues("x", "y");` will do nothing. – Ivan Stoev Jun 26 '17 at 18:43
  • @IvanStoev Can you elaborate how can I reproduce the problem where the items will be returned multiple times? `myList. ReplaceValues("x", "y");` that's a bug, thanks for pointing it out, moving the `yield` statement in the `source` foreach loop would fix it. It's not a void because this will block the collection from later being accessed by LINQ or other `IEnumerable` methods. Which is often the case with this type. – Deadzone Jun 26 '17 at 18:50
  • 1
    (1) I had in mind the bug that you just fixed :) (2) It's not natural though to have a mutating method which also `yield`s. The name implies *action* and it's easy to forget that yielding methods do nothing until they get `foreach`-ed, `ToList()`-ed, `Count()`-ed etc. Basically it's a method which primary purpose is the side effect, which doesn't fit in the LINQ(uery) spirit. – Ivan Stoev Jun 26 '17 at 19:01
  • @IvanStoev Thanks for your comments, but isn't my method exhibiting similar behavior to `.Select` isn't the latter using iterator internally as well? – Deadzone Jun 26 '17 at 19:04
  • 1
    It is, but `Select` converts (maps, projects) a source element to a destination element (by either selection a property or creating a *new* object). While your method is mutating source. Normally people do not expect a code like `foreach (var x in something)` to mutate the content of `x`. No LINQ method does that, although they can be abused to do that, which at the end will just cause issues to the one who is abusing them - I've seen that many times on SO when people try to use the "concise" LINQ `Select` + other operators to perform mutation and simply forget that it needs to be executed :) – Ivan Stoev Jun 26 '17 at 19:16
0

this will take care of it:

   static void Main(string[] args)
    {

        var list= new List<Class1>();
        var class1 = new Class1 {P1 = "P1-1", P2 = "P2-1", P3="null"};
        list.Add(class1);
        var class2 = new Class1 { P1 = "P1-2", P2 = "P2-2", P3 = "null" };
        list.Add(class2);


        foreach (var item in list)
        {
            var props2 = from p in item.GetType().GetProperties()
                        let attr = p.GetValue(item)
                        where attr != null && attr.ToString() == "null"
                        select p;

            foreach (var i in props2)
            {
                i.SetValue(item, string.Empty);
            }
        }
    }

UPDATE::::

here is a more efficient way of doing it.

    static void Main(string[] args)
    {

            var list = new List<Class1>();
            var class1 = new Class1 {P1 = "P1-1", P2 = "P2-1", P3 = "null"};
            list.Add(class1);
            var class2 = new Class1 {P1 = "P1-2", P2 = "P2-2", P3 = "null"};
            list.Add(class2);



           var updatedList=  ReplaceValues(list, "null", string.Empty);

    }

    private static IEnumerable<TSource> ReplaceValues<TSource>
    (IEnumerable<TSource> source, object oldValue,
    object newValue)
    {
        var properties = typeof(TSource).GetProperties();
        var sourceToBeReplaced = source as TSource[] ?? source.ToArray();
        foreach (var item in sourceToBeReplaced)
        {
            foreach (var propertyInfo in properties.Where(t => Equals(t.GetValue(item), oldValue)))
            {
                propertyInfo.SetValue(item, newValue);
            }
        }

        return sourceToBeReplaced;
    }
eric_eri
  • 699
  • 11
  • 19