Casting from interface to base type

Considering an earlier question on SO, I started thinking about the situation when a class provides a value, such as a collection, as an interface implemented by a value type. In the code example below, I use List and expose this list as IEnumerable.

List display through the IEnumerable interface determines the intention to list only the list, not change. However, since an instance can be re-listed, the list itself, of course, can be modified.

I also include a version of the method in the sample that prevents modification by copying links to list items to a new list each time the method is called, thereby preventing changes to the base list.

So my question is, should all the code expose a particular type, like an implemented interface, do this using a copy operation? Will there be a value in the language construct that explicitly states "I want to expose this value through the interface, and the calling code should use this value only through the interface"? What methods do others use to prevent unintended side effects like these when exposing specific values โ€‹โ€‹through their interfaces.

Please note: I understand that the behavior that is illustrated is the expected behavior. I am not saying that this behavior is incorrect, simply because it allows you to use functionality other than expressed intent. Perhaps I attach too much importance to the interface, considering it to be a limitation of functionality. Thoughts?

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TypeCastTest { class Program { static void Main(string[] args) { // Demonstrate casting situation Automobile castAuto = new Automobile(); List<string> doorNamesCast = (List<string>)castAuto.GetDoorNamesUsingCast(); doorNamesCast.Add("Spare Tire"); // Would prefer this prints 4 names, // actually prints 5 because IEnumerable<string> // was cast back to List<string>, exposing the // Add method of the underlying List object // Since the list was cast to IEnumerable before being // returned, the expressed intent is that calling code // should only be able to enumerate over the collection, // not modify it. foreach (string doorName in castAuto.GetDoorNamesUsingCast()) { Console.WriteLine(doorName); } Console.WriteLine(); // -------------------------------------- // Demonstrate casting defense Automobile copyAuto = new Automobile(); List<string> doorNamesCopy = (List<string>)copyAuto.GetDoorNamesUsingCopy(); doorNamesCopy.Add("Spare Tire"); // This returns only 4 names, // because the IEnumerable<string> that is // returned is from a copied List<string>, so // calling the Add method of the List object does // not modify the underlying collection foreach (string doorName in copyAuto.GetDoorNamesUsingCopy()) { Console.WriteLine(doorName); } Console.ReadLine(); } } public class Automobile { private List<string> doors = new List<string>(); public Automobile() { doors.Add("Driver Front"); doors.Add("Passenger Front"); doors.Add("Driver Rear"); doors.Add("Passenger Rear"); } public IEnumerable<string> GetDoorNamesUsingCopy() { return new List<string>(doors).AsEnumerable<string>(); } public IEnumerable<string> GetDoorNamesUsingCast() { return doors.AsEnumerable<string>(); } } } 
+6
c #
source share
4 answers

One way to prevent this is to use AsReadOnly () to prevent such vileness. I think the real answer is that you should never rely on anything other than an open interface / contract in terms of return types, etc. Doing something else is not encapsulated, does not allow you to replace your implementations with others that do not use List, but instead just T [], etc. etc.

Edit:

And top-down casting, as you mention, is basically a violation of the Liskov Signature Principle in order to get everything technical and so on.

+5
source share

In this situation, you can define your own collection class that implements IEnumerable<T> . Inside, your collection may contain a List<T> , and then you can simply return the enumerator of the main list:

 public class MyList : IEnumerable<string> { private List<string> internalList; // ... IEnumerator<string> IEnumerable<string>.GetEnumerator() { return this.internalList.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.internalList.GetEnumerator(); } } 
+2
source share

An interface is a restriction on the implementation of the minimum set of things that it must perform (even if "doing" is nothing more than NotSupportedException or even a NotImplementedException ). This is not a limitation that either prevents the implementation from doing more or the call code.

+2
source share

One thing that I learned while working with .NET (and with some people who quickly switch to a hacking solution) is that if nothing else, reflection often allows people to pass on your โ€œdefensesโ€.

Interfaces are not iron programming shackles, they promise that your code will do any other code saying, "I can definitely do this." If you โ€œcheatโ€ and throw the interface object into some other object, because you, the programmer, know something that is not in the program, then you are breaking this contract. The consequence is lower maintainability and confidence that no one has ever messed up anything in this execution chain, so that any other object is not sent down, which is not displayed correctly.

Other tricks, such as doing read-only things or hiding the actual list behind the shell, are just spaces. You can easily dig into the type using reflection to pull out a private list if you really wanted to. And I think that there are attributes that you can apply to types so that people do not reflect in them.

Similarly, read-only lists are actually. I could probably figure out a way to modify the list itself. And I can almost certainly change the items in the list. So readonly is not enough, nor is it a copy or an array. You need a deep copy (clone) of the source list in order to protect the data to some extent.

But the real question is: why are you fighting so hard with the contract you wrote. Sometimes ad hacking is a convenient way to work around a problem when some other library is poorly designed and does not reveal what it needs (or an error requires you to start digging to fix it.) But when you control the interface and the consumer interface, there are no excuses for not making the open interface as reliable as you need to get your work done.

In short: If you need a list, do not return IEnumerable, return the list. If you have IEnumerable, but you really need a list, then it is safer to make a new list from this IEnum and use it. There are very few reasons (and even fewer, maybe not, good reasons) to refuse the type simply because "I know that this is really a list, so it will work."

Yes, you can take steps to try to stop people from doing this, but 1) the more you fight people who insist on breaking the system, the harder they will try to break it, and 2) they only look for more rope, and ultimately they will end up with enough to hang themselves.

+1
source share

All Articles