Protect a List in a class from add/remove in c#

David DB
David DB used Ask the Experts™
on
How can I protect a list in a class to not have items added or removed in C#?

I have a class Header that consists of a list of Line

public class Line
{

}
public class Header
{
   public List<Line> Lines;
}

Open in new window


I want consumers to able to edit the individual Lines, but not Add or Remove Lines items. This because Header must have full control on Add and Remove (of Lines) because of processing rules. It should not allowed for a consumer to initialize Lines either.

I still want consumers to have all the other benefits of a List.

I dont want to create an error message, but to hide these methods from consumers of the class.
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
ste5anSenior Developer

Commented:
Implement IList in Header and redirect the methods to your backing field. E.g.

public class Line { }

public class Header : IList<Line>
{
    public Header(string lines)
    {
        this.lines = new List<Line>();
        // intialize it somehow.
    }

    private List<Line> lines;
    public Line this[int index] { get => ((IList<Line>)lines)[index]; set => ((IList<Line>)lines)[index] = value; }
    public int Count => ((IList<Line>)lines).Count;
    public bool IsReadOnly => ((IList<Line>)lines).IsReadOnly;
    public void Add(Line item) { throw new MethodAccessException("Not allowed."); }
    public void Clear() { throw new MethodAccessException("Not allowed."); }
    public bool Contains(Line item) { return ((IList<Line>)lines).Contains(item); }
    public void CopyTo(Line[] array, int arrayIndex) { ((IList<Line>)lines).CopyTo(array, arrayIndex); }
    public IEnumerator<Line> GetEnumerator() { return ((IList<Line>)lines).GetEnumerator(); }
    public int IndexOf(Line item) { return ((IList<Line>)lines).IndexOf(item); }
    public void Insert(int index, Line item) { throw new MethodAccessException("Not allowed."); }
    public bool Remove(Line item) { throw new MethodAccessException("Not allowed."); }
    public void RemoveAt(int index) { throw new MethodAccessException("Not allowed."); }
    IEnumerator IEnumerable.GetEnumerator() { return ((IList<Line>)lines).GetEnumerator(); }
}

Open in new window

Commented:
Exactly as ste5an has stated.

-saige-

Author

Commented:
Very helpful.

The only problem is that there is no reference to Lines in Header. So if I get Header.Count() I will get the number of Lines?. Not very clear.

And I will have more types in Header. Let's say Addresses with the same restrictions as Lines.

I will then implement AddLine and AddAdress methods inside Header to have full control over the creation of those.

How will the new code be then (implementing Addresses and AddLines/AddAdress) ?
ste5anSenior Developer

Commented:
The only problem is that there is no reference to Lines in Header.
D'oh??

So if I get Header.Count() I will get the number of Lines?. Not very clear.
public int Count => ((IList<Line>)lines).Count;

Open in new window


And I will have more types in Header. Let's say Addresses with the same restrictions as Lines.
You know, that you have now changed the requirements fundamentally?

Something like:

class Address {}
class Addresses : IList<Address> { // implementation as in first answer for header. }
class Line {}
class Lines : IList<Line> { // implementation as in first answer for header. }

class Header
{
	public Header() { // intialize your backing fields. }
	public Adresses Addresses { get; private set; }
	public Lines Lines { get; private set; }
}

Open in new window

Author

Commented:
Hi again.

We are getting closer.

private set does mean that consumer cannot change a property ? I want them to be able to change a property.

And how can I add an Address/Line inside Header without the consumer being able to do that directly on Address/Line ?

The reasoning behind all this is that I want to be able to do a lot of checking when a request to add Address or Line is taking place. I dont want the consumer to do this directly (with Address.Add() where I will have no control). So I want to implement a AddAdress and AddLine method in Header.

BTW: This is not the actual things I'm going to do in my code, but serves as an example of what I need to accomplish. When this is fixed I can do what I need to do.
ste5anSenior Developer

Commented:
hmm, without knowing the use-case, it's hard to say what's proper solution can look like.

But I normally start by in such a case by using validation. Each class has its IsValid method. So the Address and Line classes have an IsValid and the Header has it also. The Header can only return true, when all objects (addresses and lines) are valid and when the higher level constraints are met.

Then you pass a header to further processing only when it's valid.
Architect - Coder - Mentor
Commented:
Hi,

You can use ReadOnlyCollection (https://docs.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.readonlycollection-1?view=netframework-4.8), or create a custom class that implements IReadOnlyList<T>.

Author

Commented:
Closer and closer :-)

IsValid built-in or do I have to program it myself ?
ste5anSenior Developer

Commented:
You have do do it yourself. You may use DataAnnotaions for certain valdation thou.

Author

Commented:
The ReadOnlyCollection was the perfect solution :-)

I think I have looked into it before. The ReadOnly is very misguided since you can write to the collection, but not add or remove.

Just what I wanted!

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial