简体   繁体   中英

How to determine if all properties of type List<T> in an object are null or empty?

I have an object which contains few int/string properties and some List<T> properties where T are some other classes in the project itself. Is there a cleaner way to determine if only those List<T> properties are empty or null? Maybe using a Linq statement?

I tried searching about it but can't find a short and clean way. Should i opt for reflection?? Can someone provide a sample related to this?

public class A
{
   ..some properties..
   List<ClassA> ListA { get; set; }
   List<ClassB> ListB { get; set; }
   List<ClassC> ListC { get; set; }
   List<ClassD> ListD { get; set; }
   ..some properties..
}

EDIT 1: So far i have managed to write a clean code to check if list properties are null. But how can i check if they are empty. I need to convert object to List but i dont know the type of List it is

var matchFound = myObject.GetType().GetProperties()
                        .Where(x => x.PropertyType == typeof(List<>))
                        .Select(x => x.GetValue(myObject))
                        .Any(x => x != null);

EDIT 2: I ended up using this, a one liner that works fine:

var matchFound = myObject.GetType().GetProperties()
                        .Where(x =>(x.GetValue(myObject) as IList)?.Count()>0);

Here is what i would do.

    /// <summary>
    /// caching a Dyctionary of IList types for faster browsing
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    private static readonly Dictionary<Type, Type> CachedActualType = new Dictionary<Type, Type>();
    // Get Internal type of IList.
    // When the type is not a list then it will return the same type.
    // if type is List<T> it will return the type of T
    public static Type GetActualType(this Type type)
    {
        if (CachedActualType.ContainsKey(type))
            return CachedActualType[type];

        if (type.GetTypeInfo().IsArray)
            CachedActualType.Add(type, type.GetElementType());
        else if (type.GenericTypeArguments.Any())
            CachedActualType.Add(type, type.GenericTypeArguments.First());// this is almost always find the right type of an IList but if it fail then do the below. dont really remember why this fail sometimes.
        else if (type.FullName?.Contains("List`1") ?? false)
            CachedActualType.Add(type, type.GetRuntimeProperty("Item").PropertyType);
        else
            CachedActualType.Add(type, type);

        return CachedActualType[type];
    }

And then

var matchFound = myObject.GetType().GetProperties()
                        .Where(x => x.PropertyType.GetActualType() != x.PropertyType && 
                              (x.GetValue(myObject) as IList)?.Count()>0);

You can actually do even better and dont need to check for type and only try to cast the value. The value will always be null if the type is not an IList

var matchFound = myObject.GetType().GetProperties()
                        .Where(x =>(x.GetValue(myObject) as IList)?.Count()>0);

You can use reflection for your requirement, i have just tried it.

       class Test
       {

       }
        class UserDetails
        {
            public List<Test> Test1 { get; set; }
            public List<Test> Test2 { get; set; }
            public string firstname { get; set; }
            public string surname { get; set; }
            public string city { get; set; }
            public string state { get; set; }
        }

Use this query to search, you can customize where condition for your requirement

UserDetails yourObject = new UserDetails();
            yourObject.Test1 = new List<Test> { new Test() };

            var result = typeof(UserDetails).GetProperties()
                .Select(prop => prop)
                .Where(property =>
                {
                    if (property.PropertyType == typeof(List<Test>))
                    {
                        var value = (List<Test>)property.GetValue(yourObject, null);
                        return value == null || value.Count == 0;
                    }

                    return false;
                }).ToList(); // this will return 1 because 1 property has count > 1

Update if use Templete

class UserDetails<T>
    {
        public List<T> Test1 { get; set; }
        public List<T> Test2 { get; set; }
        public string firstname { get; set; }
        public string surname { get; set; }
        public string city { get; set; }
        public string state { get; set; }
    }

Query

UserDetails<Test> yourObject = new UserDetails<Test>();
            yourObject.Test1 = new List<Test> { new Test() };

            var result = typeof(UserDetails<Test>).GetProperties()
                .Select(prop => prop)
                .Where(property =>
                {
                    if (property.PropertyType == typeof(List<Test>))
                    {
                        var value = (List<Test>)property.GetValue(yourObject, null);
                        return value == null || value.Count == 0;
                    }

                    return false;
                }).ToList();

You need quite a bit of reflection:

// the type to test
public class TestData
{
    public string A { get; set; }
    public List<string> B { get; set; }
    public List<int> C { get; set; }
}

// an helper class used to generate checking functions
public static class ListTester
{
    public static Func<T, bool> MakeClassChecker<T>()
        where T : class
    {
        var checkFunctions = EnumerateListProperties<T>()
            .Select(MakePropertyChecker<T>)
            .ToList();

        return instance => checkFunctions.All(f => f(instance));
    }

    public static IEnumerable<PropertyInfo> EnumerateListProperties<T>()
    {
        return typeof(T).GetProperties(Instance | Public | NonPublic)
            .Where(prop => IsListClosedType(prop.PropertyType));
    }

    public static Func<T, bool> MakePropertyChecker<T>(PropertyInfo prop)
        where T : class
    {
        var propType = prop.PropertyType;
        var listItemType = propType.GenericTypeArguments[0];

        var listEmptyChecker = (Func<object, bool>) ListCheckerFactoryMethod
            .MakeGenericMethod(listItemType).Invoke(null, new object[0]);

        return instance => instance != null && listEmptyChecker(prop.GetValue(instance));
    }

    private static MethodInfo ListCheckerFactoryMethod
        = typeof(ListTester).GetMethod(nameof(ListCheckerFactory), Static | Public);


    public static Func<object, bool> ListCheckerFactory<T>()
    {
        return list => list == null || ((List<T>) list).Count == 0;
    }

    public static bool IsListClosedType(Type type)
    {
        return type != null &&
                type.IsConstructedGenericType &&
                type.GetGenericTypeDefinition() == typeof(List<>);
    }
}

[Test]
public void TestTemp()
{
    var props = ListTester.EnumerateListProperties<TestData>();
    CollectionAssert.AreEquivalent(props.Select(prop => prop.Name), new[] {"B", "C"});

    var allListsAreNullOrEmpty = ListTester.MakeClassChecker<TestData>();

    Assert.That(allListsAreNullOrEmpty(new TestData()), Is.True);
    Assert.That(allListsAreNullOrEmpty(new TestData() {B = new List<string>()}), Is.True);
    Assert.That(allListsAreNullOrEmpty(new TestData() {B = new List<string>() {"A"}}), Is.False);
}

Now, for the important bits: you search for properties of closed generic types of List<> . The selection of the properties is done in IsListClosedType . Then, for each property, we make a checking function using MakePropertyChecker .

The job of MakePropertyChecker is to build via MakeGenericMethod a version of ListCheckerFactory of the appropriate type.

You want to check all properites that are of type List<something> This method can do the trick:

bool IsGenericList(Type t)
{
    return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>);
}

Now you can modify your Linq query so it returns if at least one of List members is not null or empty

var matchFound = myObject.GetType().GetProperties()
    .Where(p => IsGenericList(p.PropertyType))
    .Select(p => p.GetValue(myObject) as IEnumerable)
    .Any(list => list != null && list.Cast<object>().Any());//Cast<object> needed to be able to use Linq Any()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM