Sierramike
Sierramike

Reputation: 113

How to iterate thru an array without knowing the underlying type in C#

Let's say I have a collection of objects, some of which are arrays.

I would like to concat all the values to print them, but I don't know the type of elements in the arrays.

foreach(var item in myList)
{
    var val = item.Property;
    if (val.GetType().IsArray)
    {
        var array = val as IEnumerable<object>;
        val = string.Join(",", array);
    }
    DoSomething(val);
}

If val contains a string[], this code snippet will work, also for a myClass[].

But if it contains a int[] or double[], then array will be null, meaning the dynamic cast failed.

If int or double are inherited from System.TypeValue, which inherits from object, why don't this code snippet work?

And how could I achieve that?

Edit: changed the code snippet to be more explicit and avoid the wrong variable usage that did showup because I wrongly simplified my code.

Upvotes: 1

Views: 2496

Answers (4)

Michael Puckett II
Michael Puckett II

Reputation: 6749

If you're just worried about arrays in general, or better yet IEnumerable (as per your cast) then I suggest leaving the reflective portion of checks out and simply attempting to convert to IEnumerable.

foreach (var val in myList)
{
    if (val is IEnumerable enumeration)
    {
        string.Join(",", array); //You can't use val (as per your example because it's the element of the loop)
    }
}

I honestly don't know what you're intending to do with the array, as your adding it to a string as an object, which will just be array, array, array... and so on pretty much. I'm going to post a better solution however I'm not sure if it's what you want or not because what you're doing above doesn't make a lot of sense.

var notSureWhyStringBuilder = new StringBuilder();
foreach (var val in myList)
{
    if (val is IEnumerable enumeration)
    {
        notSureWhyStringBuilder.Append($",{enumeration.ToString()}");
    }
}
Console.WriteLine(notSureWhyStringBuilder.ToString());

Now with this at least I feel like you're more in the direction that you want to be in but that doesn't sit well with me because I don't know what you're going to gain out of it.

I'm going to post one more example, one that will iterate and build the inner enumeration into the string for you. I don't know or assume that's what you want but between the 3 examples I'm providing hopefully you can take away and re-engineer it to get what you need and possibly learn from it.

var notSureWhyStringBuilder = new StringBuilder();
foreach (var val in myList)
{
    if (val is IEnumerable enumeration)
    {
        foreach (var innerEnumeration in enumeration)
        {
            notSureWhyStringBuilder.Append($",{innerEnumeration.ToString()}");
        }
    }
}
Console.WriteLine(notSureWhyStringBuilder.ToString());

Here's a small console app that I put together for you to piddle with. Copy and paste it as is and it should work.

Console App

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Question_Answer_Console_App
{
    class Program
    {
        private const string ShortTab = "  ";
        private static readonly List<object> ListOfObjects = new List<object>()
        {
            4,
            "Michael",
            new object(),
            new Program(),
            69.4,
            new List<string>() {"Mathew", "Mark", "Luke", "John" },
            new int[] { 1, 3, 5, 7, 9 }
        };

        static void Main(string[] args)
        {
            var itemsText = new StringBuilder();
            var arrayCounter = 0;

            foreach (var item in ListOfObjects)
            {
                if (item is IEnumerable enumeration)
                {
                    itemsText.AppendLine($"Array: {++arrayCounter}");
                    if (item is string text)
                    {
                        itemsText.AppendLine($"{ShortTab}{text}");
                    }
                    else
                    {
                        foreach (var innerItem in enumeration) itemsText.AppendLine($"{ShortTab}{innerItem.ToString()}");
                    }
                }
            }
            Console.WriteLine(itemsText.ToString());
            Console.Read();
        }
    }
}

Outputs

Array: 1
  Michael
Array: 2
  Mathew
  Mark
  Luke
  John
Array: 3
  1
  3
  5
  7
  9

Upvotes: 1

armenm
armenm

Reputation: 932

You cannot cast a value-type array to an IEnumerable because variance only applies if there's no representational conversion needed. Therefore it doesn't apply for value types. In this particular case this conversion would mean boxing.

I would do it this way to prevent unnecessary boxing, like in the case with Cast, for example.

public static string Stringify(this object o, string delimiter)
{
    if (!(o is IEnumerable enumerable))
        return o.ToString();

    var sb = new StringBuilder();

    foreach (var i in enumerable)
    {
        if (sb.Length > 0)
            sb.Append(delimiter);

        sb.Append(i.ToString());
    }

    return sb.ToString();
}

Upvotes: 0

thisextendsthat
thisextendsthat

Reputation: 1335

First off, trying to assign to val inside the foreach loop will not work. You can't change a collection you are iterating over.

So you need to build up a new collection. Something like this works, see how the yield return statement in the iterator lets you build up a new IEnumerable of the leaf objects and works for both objects and value types at any level.

    class Program
    {
        static void Main(string[] args)
        {
            var myWackyList = new object[] {
                new[]{1d, 2d},
                3d,
                new[]{4d, 5d},
                new []
                {
                    new[]
                    {
                        6d
                    }
                },
                "7",
                new[]{ "8", "9"}
            };

            Console.WriteLine( string.Join(", ", Flatten( myWackyList )));
        }

        static IEnumerable<object> Flatten(IEnumerable enumerable)
        {
            foreach (var val in enumerable)
            {
                if ( val.GetType().IsArray )
                    foreach ( var flattenedVal in Flatten( val as IEnumerable ) )
                        yield return flattenedVal;
                else
                    yield return val;
            }
        }
    }

Upvotes: 0

Krypt1
Krypt1

Reputation: 1066

It's not allowed to do so in C#, more details here. But you can cast to non-generic IEnumerable, and then cast everything to object before pushing to string.Join():

foreach(var val in myList)
{
    if (val.GetType().IsArray)
    {
        var array = (IEnumerable)val;
        // It's not allowed to set val variable, but I assume it's just an example
        val = string.Join(",", array.Cast<object>());
    }
}

Upvotes: 2

Related Questions