Search code examples
c#listparameter-passing

IEnumerable as constructor parameter when list is kept in class


If I want to store the list that the class is being passed as a parameter, what parameter type should I use?

Should I take an IEnumerable and do something like Items = items.ToList().AsReadOnly()? But how do I avoid superfluous copies? What if the list was created inside the constructor call, that's definitely a ToList() too many then.

In C++ I would have a non-reference parameter to make sure a copy of the list is passed, but AFAIK in C# that's not possible.

One example I have in mind is an event arguments class that contains a list of something, that is also the current state of the invoking class. The lists should not change after they have been passed to the various event handlers.

Creating a copy inside the constructor is fine when is is called like this:

SomethingHappened?.Invoke(this, new CustomEventArgs(_items));

But when it is called like this, a unnecessary copy is made:

SomethingHappened?.Invoke(this, new CustomEventArgs(new List { item1, item2 }));

Solution

  • If you don't want to modify the list which is passed in to your constructor in your class, then you could define the parameter as an IReadOnlyList<T> and pass it by calling AsReadOnly() on the original list. You'll still use the same list, but AsReadOnly() provides a wrapper which prevents modification, as per the documentation:

    public class YourClass
    {
        private IReadOnlyList<SomeType> yourList;
    
        public YourClass(IReadOnlyList<SomeType> list)
        {
            yourList = list;
        }
    }
    

    Then call the constructor as follows:

    var someList = new List<SomeType>();
    
    var yourClassInstance = new YourClass(someList.AsReadOnly());
    

    You can still change the original list reference and it will be reflected in the read-only reference, since they're actually accessing the same list, but you won't be able to modify the list inside YourClass.

    Alternatively, if you cannot call AsReadOnly() on the outside, then you need to call it inside your constructor, which would technically allow your class to make modifications before storing the read-only reference:

    public class YourClass
    {
        private IReadOnlyList<SomeType> yourList;
    
        public YourClass(IList<SomeType> list)
        {
            yourList = list.AsReadOnly();
        }
    }
    

    If you do need to modify the passed list after all, you'll need to make a copy, by which YourClass would then take ownership of the copy:

    public class YourClass
    {
        private IList<SomeType> yourList;
    
        public YourClass(IList<SomeType> list)
        {
            yourList = new List<T>(list);
        }
    }
    

    Since the callee cannot know on its own whether a copy should be made, you could add an optional flag to the constructor to transfer the responsibility to the caller:

    public class YourClass
    {
        private IList<SomeType> yourList;
    
        public YourClass(IList<SomeType> list, bool makeCopy = false)
        {
            yourList = makeCopy ? new List<T>(list) : list;
        }
    }
    

    This way, the caller must decide whether a copy is required:

    var someList = new List<SomeType>();
    
    //Make a copy explicitly
    var yourClassInstance1 = new YourClass(someList, true);
    
    //Do not make a copy
    var yourClassInstance2 = new YourClass(new List<SomeType>{ object1, object2 });