2

Despite there are some information in answers to these questions: Cast IList to List and Performance impact when calling ToList() they do not answer my specific question. I have a class that is a wrapper around List. This class was designed to be sent via WCF services and implements some additional functionality.

[DataContract]
public class DataContractList<T> : IList<T>
{
    [DataMember]
    protected readonly List<T> InnerList;

    public DataContractList()
    {
        InnerList = new List<T>();
    }

    public DataContractList(IList<T> items)
    {
        InnerList = items as List<T> ?? items.ToList(); //Question is about this line.
    }
}

So there is a constructor that accepts an IList<T> interface (in order to encourage programming to interface). I need to convert this IList<T> interface to List<T> class. I can use .ToList() extension method which internally creates a new instance of List<T> by passing IEnumrable "this" argument to it's constructor (see here). Generic List<T> constructor just iterates through this enumberable. So I would like to prevent this iteration if it's not necessary (in case inner items parameter is already a List<T>). So, is it an optimal way (in terms of performance and readability) to do this: InnerList = items as List<T> ?? items.ToList();? If no, please provide with details about better way and why.

Community
  • 1
  • 1
Vova
  • 1,356
  • 1
  • 12
  • 26
  • Dont inherit from List or IList. Create a new class that has an IList as a property. http://stackoverflow.com/q/21692193/325727 http://stackoverflow.com/q/25604406/325727 http://stackoverflow.com/q/14559070/325727 http://stackoverflow.com/q/31456710/325727 – JK. Oct 01 '15 at 21:12
  • 1
    Why don't you use `protected readonly IList InnerList;` – Jakub Lortz Oct 01 '15 at 21:13
  • BTW, questions in the form of "Is there a better way to X" are usually too broad and usually get down voted and closed. – JK. Oct 01 '15 at 21:14
  • @ Jakub Lortz: because WCF can not create wsdl for interfaces. – Vova Oct 01 '15 at 21:15
  • @JK: I think my question is quite specific. – Vova Oct 01 '15 at 21:16
  • 1
    What's wrong with `InnerList = items as List ?? items.ToList();` in the first place? – Lucas Trzesniewski Oct 01 '15 at 21:18
  • Why wouldn't you just make your field type `IList` so that you are programming to the interface as well? – Ed T Oct 01 '15 at 21:21
  • 1
    One issue could be the semantics of passing a `List` vs. some other `IList`. In the first case you will end up with a reference to the underlying list that will see updates as you change that object. In the other case you will end up with a copy of the list that won't change as the underlying IList changes. – Mike Zboray Oct 01 '15 at 21:21
  • @Lucas Trzesniewski: Right, so if items is not a List there is unnecessary operation casting using `as` operator. So what does have more performance hit: casting or iteration through the array if array is not more then 64 elements) – Vova Oct 01 '15 at 21:23
  • 1
    @Vova casting is a *very* cheap operation. – Lucas Trzesniewski Oct 01 '15 at 21:29

4 Answers4

3

To try to avoid iterating the list is a good intention, but there is more to consider.

You have protected the InnerList propety from public access, if you just assign the list to that property then that effort is pointless. If I send a list into the constructor and keep a reference to that list, then I have a reference to the list that the class uses internally:

List<sting> list = new List<string>();
var dc = new DataContractList(list);

// now I can manipulate the internal list:
list.Add("Woaaah! Where did that come from?");

To keep the internal list internal, you would always create a new list even if the input is a list:

public DataContractList(IList<T> items)
{
    InnerList = new List<T>(items);
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • It's a good point @Guffa. But probably what is worse is that if I will modify my DataContractList after it's created there will be a side effect for original list. So it seems that it's better not to try to improve performance. Thanks! – Vova Oct 01 '15 at 21:41
  • That's a very important point. However, I think that your solution only protects the list, but it does not protects the items. I mean I can still do something like `items[0] = Woaaah! Where did that come from?");`. – Racil Hilan Oct 01 '15 at 21:51
1

You noticed that the WCF default serialisation doesn't work with arbitrary IList<T> implementations. That's good.

You then decide to solve that by forcing the use of List<T>. That's not so good.

Don't use the default serialisation if it doesn't do what you want. Use custom serialisation. You can make your class serialise item by item if you know that that's what you want to do, even if the inner container doesn't know about it.

And to answer your likely follow-up question: a quick search shows the basic approach of how to do that right here on Stack Overflow: How to use Custom Serialization or Deserialization in WCF to force a new instance on every property of a datacontact ?

Community
  • 1
  • 1
1

Just to complete Guffa's answer. As you mentioned, using

InnerList = new List<T>(items);

or

InnerList = items.ToList();

is basically one and the same, so I would prefer the later from readability point of view.

However, the following

Generic List constructor just iterates through this enumberable.

is not true, as you may see here http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,d2ac2c19c9cf1d44, which in turn is using http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,0c418e0fac68ada2 when the argument is of type List<T>. Shortly, you should not be concerned about performance.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • So do you mean, that `Array.CopyTo` is more efficient then using Enumerator? – Vova Oct 02 '15 at 13:50
  • 1
    @Vova Absolutely. Almost all BCL data structures use arrays internally, and `Array.Copy` methods are the fastest possible for that. – Ivan Stoev Oct 02 '15 at 14:18
0

IMHO, if you inherit from IList<T> and then force using List<T>, it defeats the purpose, doesn't it?

The idea of using an interface is to support different classes where only the shared functionality matters.

In your case, your DataContractList class accepts any class that implements IList<T>, however if I create a class that implements IList<T> but cannot be converted to List<T>, I will get a run-time error, won't I?

I'd suggest that you inherit from List<T> if your DataContractList class can only support that implementation of the interface.

Alternatively, if your DataContractList class can really support any implementation of IList<T>, then change it to:

[DataContract]
public class DataContractList<T> : IList<T>
{
    [DataMember]
    protected readonly IList<T> InnerList;

    public DataContractList()
    {
        InnerList = new List<T>();
    }

    public DataContractList(IList<T> items)
    {
        InnerList = items;
    }
}
Racil Hilan
  • 24,690
  • 13
  • 50
  • 55
  • 1
    It's a WCF data contract - it has to use concrete types for serialization IIRC. – Lucas Trzesniewski Oct 01 '15 at 21:31
  • I tried to inherit my custom list from List but it was again was a problem with WCF (I don't remember which problem). If some implementation of IList can not be converted to List it then will go to .ToList() extension, will not it? Why I will get a runtime error here? And WCF is not able to generate WSDL for interface members. – Vova Oct 01 '15 at 21:31
  • @LucasTrzesniewski Yes, that's obvious, which is why I recommended inheriting from `List`. But I was trying to make my answer more general as to when to use interfaces vs. concrete class. – Racil Hilan Oct 01 '15 at 21:35
  • 1
    @Vova I have to see the problems that you faced with inheriting from `List` before I can help with that. As for the your question about the `.ToList() `, it all comes down to how you will use the passed instance. Like I said, if you only use the common properties/methods defined by the in the interface, then of course you'll be fine. But if you try to use some property/method that does not exist in the passed object, then you will get a run-time error. That's the concept behind using interfaces, you limit yourself to using the common properties/methods of the interface. – Racil Hilan Oct 01 '15 at 21:43