简体   繁体   中英

enumerate through all IEnumerables

I need to send different IEnumerables to an Printer object. This printer object will then do something to them, inside a foreach loop.

class Printer
{
    public Printer(IEnumerable list)
    {
        foreach (var enumerable in list)
        {
            //DO STUFF
        }
    }
}

This lets me send any enumerable, such as an List<T> to the printer object. such as

    var list = new List<string> {"myList"};
    new Printer(list); //mylist

This works fine. BUT if I send a Dictionary<T, T> such as:

var dictionary = new Dictionary<int, string> {{1, "mydict"}};
new Printer(dictionary); //[1, mydict]

It'll have a key and a value. What I would want though, would be separate access to the Value property inside the foreach loop. All I DO have access to is the enumerable object, which has no properties I can use.

Now what if the datatype T is an object containing several properties (this goes for both examples). How would I be able to use these properties in my foreach loop?

Do I honestly have to create an overload of the constructor, foreach possible datatype I might send down to it?

Also, all I need to do in the foreach is not dependable to any datatypes - as it won't manipulate everything. I do need ACCESS to all the properties though.

Also, this is just example code, not actually the production-code I use in my application.

Can you change the code of the Printer class? If it accepted something like an IEnumerable<IPrintable> instead of just an IEnumerable it would be easier. With an interface like this:

interface IPrintable
{
    void Print();
}

Then all objects that would be sent to the Printer would need to implement that interface. Then you could do:

class Printer
{
    public Printer(IEnumerable<IPrintable> list)
    {
        foreach (var enumerable in list)
        {
            enumerable.Print();
        }
    }
}

And if you have a dictionary of printable objects, something like:

var dict = new Dictionary<int,IPrintable>();

You could just pass the values to the function:

var printer = new Printer(dict.Values);

You could modify your method to accept a delegate that returns the data the print method needs. Something like this:

// You will not need this class, if you always want a single string result.
class PrinterData
{
    public string Value { get; set; }
    // More properties?
}
class Printer
{
    public Printer<T>(IEnumerable<T> list, Func<T, PrinterData> func)
    {
        foreach (T item in list)
        {
            PrinterData data = func(item);
            // Do something with the data.
        }
    }
}

Usage:

int[] ints = new int[] {1,2,3};
new Printer().Print(ints, x => new PrinterData() { Value = x.ToString() });

var dictionary = new Dictionary<int, string> {{1, "mydict"}};
new Printer().Print(dictionary, x => new PrinterData() { Value = x.Name + " = " + x.Value });

Per Erik Stendahl's answer is very similar.

You have to extract an enumerable with the values you want to pass before you call new Printer(). In the case of the dictionary this is simple: just use dict.Values. A more general case is:

var list = List<MyObject>()...

var printer = new Printer(list.Select(x => x.MyProperty));

If you want to treat different types differently, you probably should make different methods. If you want to treat them the same, you should accept a common interface, and only use the methods defined for the interface.

It would be possible to do

if (list is Dictionary<int, string>) {
    // do special case
}

but I shudder at the thought.

You can even check generically:

class Printer<T>
{
    public Printer<T>(IEnumerable list)
    {
        foreach (var enumerable in list)
        {
            if (list is Dictionary<T, T>) {
                //DO STUFF
            }
        }
    }
}

The problem is that a collection, though it is enumerable, can hold different types of objects, as you saw with the difference between the List and the Dictionary .

To get around this without coding for each object type, you'd have to only accept an enumerable collection of a certain type that you define, for example IEnumerable<IMyType> .

If you can't do anything at the callee, it's up to the caller to make sure it passes an IEnumerable that is "valid" for Printer, like passing dictionary.Values instead of dictionary in your example. However if the class is public and will be used by 3rd party users, you're better to add some generic constraint to your IEnumerable , as others stated.

Here is the result: I used your guys help, so I guess I shouldn't vote my own as the answer.

class Printer
{
    public Printer(IEnumerable<IPrintable> list) //Accepts any collection with an object that implements IPrintable interface
    {
        foreach (var enumerable in list) //iterate through list of objects
        {
            foreach (var printable in enumerable)//loops through properties in current object
            {
                //DO STUFF 
            }
        }
    }
}

interface IPrintable : IEnumerable { }
class SomeObject : IPrintable
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    public interface IEnumerable
    {
        IEnumerator GetEnumerator(); //Returns a Enumerator
    }

    public IEnumerator GetEnumerator()
    {
        yield return Property1;
        yield return Property2;
    }
}

I'd naturally need to implement custom GetEnumerator() foreach object - no problem though!

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