views:

215

answers:

2

I have an Email object and am attempting to validate the number of attachments that are in its List<Attachment> property.

The trick is that we consume the Send() method across a WCF service. It's easy to validate it server side, but I want to validate it client side first.

I have generated a library that others are supposed to use in order to consume the service, which in turn has a proxy containing all the objects and available methods. I think I should be able to overload the Add() method on the GenericList with some custom code so the collection is checked when anything is added, and if it exceeds the specified maximum then an exception is thrown.

public partial class List<Attachment>
{
    public void Add(Attachment item)
    {
        base.Add(item);
        if (this.Count() > maxAttachments)
        {
            throw new Exception("fail")
        }
    }
}

This doesn't work - I can't class base.Add() and I can't define partial class with a specified type.

How do I create an overload for the Add method so that I can include some custom code?

+2  A: 

If you own the Email class, your best option would be to change the underlying type of the List member to a specialized implementation of a list.

public class Email
{
    const int MaxAttachments = /* ... */;

    public Email(/* ... */)
    {
        this.Attachments = new FixedSizeList<Attachment>(MaxAttachments);
    }

    // ...

    public IList<Attachment> Attachments
    {
        get;
        private set;
    }
}

class FixedSizeList<T> : IList<T>
{
    List<T> innerList;
    int maxCount;

    public FixedSizeList(int maxCount)
    {
        this.innerList = new List<T>(maxCount);
        this.maxCount = maxCount;
    }

    // override all the IList<T> members here by delegating
    // to your innerList ...
    // ...

    public void Add(T item)
    {
         if (this.Count == this.maxSize)
         {
             throw new InvalidOperationException("No more items can be added.");
         }

         this.innerList.Add(item);
    }

    // ...
    // ...
}

It's kind of a lot of boilerplate code, but that's really the only way to cleanly override the behavior.

However, if you don't own the Email class, you can't really do this through traditional means; you'd need a reflection hack to replace the underlying member or something like the Managed Extensibility Framework.

bobbymcr
Fantastic, thanks. This will be a headache to implement amongst all the svcutil generated code in my proxy but will definitely work.
Kirk Broadhurst
If you are using svcutil.exe, check out the /collectionType (/ct) option; that might ease a bit of the pain. See http://msdn.microsoft.com/en-us/library/aa347733.aspx .
bobbymcr
+1  A: 

List<T> is not a partial class, so you can't extend it using your own partial classes.

Also, LINQ to Objects does not provide Add() on List<T>, that's part of the IList<T> interface implemented by List<T>, and Add() is not a virtual or abstract method in List<T>, so you can't override it.

What you should look at is System.Collections.ObjectModel.Collection<T> - this component provides a list implementation similar to List<T> with the added capacity to override protected methods that give you a place to do validation tasks.

You don't have to implement the list from scratch, you just inherit from it, and override methods such as InsertItem() and RemoveItem() to implement custom rules:


using System.Collections.ObjectModel;

public class EmailCollection : Collection<Email>
{
    public int MaximumAttachments { get; set; }

    protected override void InsertItem(int index, Email item)
    {
        if (Count == MaximumAttachments)
        {
            ... throw error
        }

        // do actual insert
        base.InsertItem(index, item)
    }
}

Add() calls InsertItem() under the hood.

Sam
Agree that `Collection<T>` is a much simpler implementation for this (compared to implementing `IList<T>`); of course, it gets tricky if the external library *demands* a `List<T>` - you might need to expose the inner-list somewhere (and explicitly ensure that it *is* a `List<T>` in the ctor, otherwise it is an implementation detail that `List<T>` is the default).
Marc Gravell