tags:

views:

126

answers:

5

I've been looking around, and so far haven't managed to find a good way to do this. It's a common problem, I'm sure.

Suppose I have the following:

class SomeClass : IComparable
{ 
    private int myVal; 
    public int MyVal
    { 
        get { return myVal; } 
        set { myVal = value; }
    }

    public int CompareTo(object other) { /* implementation here */ }
}

class SortedCollection<T>
{
    private T[] data;
    public T Top { get { return data[0]; } }

    /* rest of implementation here */
}

The idea being, I'm going to implement a binary heap, and rather than only support Insert() and DeleteMin() operations, I want to support "peeking" at the highest (or lowest, as the case may be) priority value on the stack. Never did like Heisenberg, and that whole "you can't look at things without changing them" Uncertainty Principle. Rubbish!

The problem, clearly, is that the above provides no means to prevent calling code from modifying MyVal (assuming SortedCollection) via the Top property, which operation has the distinct possibility of putting my heap in the wrong order. Is there any way to prevent modifications from being applied to the internal elements of the heap via the Top property? Or do I just use the code with a warning: "Only stable if you don't modify any instances between the time they're inserted and dequeue'd. YMMV."

+1  A: 

You can have a readonly property (that is, a property with only a getter):

private int myVal;
public int MyVal { get { return myVal; } }

But be careful: this may not always work how you expect. Consider:

private List<int> myVals;
public List<int> MyVals { get { return myVals; } }

In this case, you can't change which List the class uses, but you can still call that List's .Add(), .Remove(), etc methods.

Joel Coehoorn
In that case I may return a ReadOnlyCollection depending on the situation. +1
Ed Swangren
Although that is somewhat beside the point of this thread/answer.
Ed Swangren
A: 

Only implement getters for your properties and modify the collection by having add/remove methods

Mircea Grelus
+1  A: 

Your properties don't have to have the same accessibility for get/set. This covers you for anything that returns a value type (typically structs that only contain value types) or immutable reference types.

public int MyVal
{ 
    get { return myVal; } 
    private set { myVal = value; }
}

For mutable reference types, you have other options, such as returning Clone()s or using ReadOnlyCollection<T> to keep the caller from changing them:

private List<int> data;

public IList<int> Data
{
    get { return new ReadOnlyCollection<int>(this.data); }
}
280Z28
FYI - The C# compiler supports a bit of syntactic sugar for read-only auto-properties: public int MyVal { get; private set; }. This can make your property declarations a bit cleaner if you have several of these properties on a single type.
Levi
I kinda like the clone idea... I don't want to limit the collection to only support Cloneable data types, but in the case that the type IS cloneable, conditionally supporting this kind of behavior might help assuage my fears. Thanks!
Dathan
@Levi Thanks for the info. I was including this code in a VS 2005 project, though... My understanding is that auto-properties are supported starting with 2008?
Dathan
+1  A: 

To answer your question: No, there's no way to implement the kind of behavior you want - as long as T is of reference type (and possibly even with some value-types)

You can't really do much about it. As long as you provide a getter, calling code can modify the internal contents of your data depending on the accessibility of said data (i.e. on properties, fields, and methods).

class SomeClass : IComparable
{ 
    private int myVal; 
    public int MyVal
    { 
        get { return myVal; } 
        set { myVal = value; }
    }

    public int CompareTo(object other) { /* implementation here */ }
}


class SortedCollection<T>
{
    private T[] data;
    public T Top { get { return data[0]; } }

    /* rest of implementation here */
}

//..
// calling code
SortedCollection<SomeClass> col;
col.Top.MyVal = 500;  // you can't really prevent this

NOTE What I mean is you can't really prevent it in the case of classes that you don't control. In the example, like others have stated you can make MyVal's set private or omit it; but since SortedColleciton is a generic class, you can't do anything about other people's structures..

Miky Dinescu
You hit the nail right on the head -- thanks. What I'm considering doing in this case is actually going a little further and using something like Castle DynamicProxy to create a wrapper around the sorted type, and return that wrapper as the Top element. It presents some problems, but could work in some cases. I'll probably also implement support for catching any INotifyPropertyChanged events if the type supports it and resorting the heap if appropriate, and supporting the ICloneable solution described elsewhere. That should catch a great number of cases. And if not, at least I tried.
Dathan
A: 

I understand your problem now. I think this should work:

class SortedCollection<T> where T: ICloneable
{
    private T[] data;
    public T Top 
    { 
         get 
         { 
             T ret = (T)data[0].Clone();
             return ret; 
         }
    }

    /* rest of implementation here */
}

The ICloneable constraint ensures that the type parameter implements the ICloneable interface. (if this is acceptable)

Tamás Szelei