简体   繁体   中英

Why/how does this enable me to select multiple days in a Calendar control?

I was wrestling with the fact that Calendar.SelectedDates is read-only. I wanted a way to programmatically select multiple dates in a calendar. I found the following snippet of code, which does exactly what I want, but I can't understand why it works:

protected void Page_Load(object sender, EventArgs e)
{
    SelectedDatesCollection dates = Calendar1.SelectedDates;
    dates.Add(new DateTime(2012, 5, 1));
    dates.Add(new DateTime(2012, 5, 5));
    dates.Add(new DateTime(2012, 5, 9));
}

What I would EXPECT (given my limited knowledge of C#), is completely the opposite:

protected void Page_Load(object sender, EventArgs e)
{
    ArrayList datelist = new ArrayList();
    datelist.Add(new DateTime(2012, 5, 1));
    datelist.Add(new DateTime(2012, 5, 5));
    datelist.Add(new DateTime(2012, 5, 9));
    Calendar1.SelectedDates = new SelectedDatesCollection(datelist);
}

HOW is the SelectedDates read-only property affected by a local SelectedDatesCollection variable? Why does adding to that local variable affect the Calendar1.SelectedDates property?

(this is just a drag-n-drop ASP.Net Calendar, with no altered/special properties)

The SelectedDatesCollection as a property is ReadOnly, but you can still change it as an object, as in add or remove items.

There is a difference between changing the object (aka calling it's methods with change it's data), and changing the object reference itself, as a member of a class, etc.

By doing:

SelectedDatesCollection dates = Calendar1.SelectedDates;

You are not COPYING the collection, but just saving it's reference, as in giving it another name.

Ultimately, you could also do:

protected void Page_Load(object sender, EventArgs e)
{
    Calendar1.SelectedDates.Add(new DateTime(2012, 5, 1));
    Calendar1.SelectedDates.Add(new DateTime(2012, 5, 5));
    Calendar1.SelectedDates.Add(new DateTime(2012, 5, 9));
}

It is just that one would say the other way is more readable.

dates and Calendar1.SelectedDates contain a reference to the exact same object. Calling the Add method on dates is just like calling it on Calendar1.SelectedDates , and vice-versa.

The reason you could not reassign Calendar1.SelectedDates as you try to do in your 2nd piece of code (which does not compile), is that SelectedDates is marked as read-only, as you said.

Marking a member as read-only simply means that it will only be assigned immediately or in the constructor of the class/struct/etc.

It does not, however, mean that you cannot change data within that object.

From now on it's just some extra info

There is an class ReadOnlyCollection, which does not allow to change it's ELEMENTS.

So, having a readonly ReadOnlyCollection, for example, would mean you cannot change the member, and you cannot change the elements as well.

Examples:

public class Class1
{
    public List<int> Numbers = new List<int> {1, 2, 3};
}

public class Class2
{
    public readonly List<int> Numbers = new List<int> {1, 2, 3};
}

public class Class3
{
    public ReadOnlyCollection<int> Numbers = new ReadOnlyCollection<int> {1, 2, 3};
}

public class Class3
{
    public readonly ReadOnlyCollection<int> Numbers = new ReadOnlyCollection<int> {1, 2, 3};
}

Differences:

var c1 = new Class1();
var c2 = new Class2();
var c3 = new Class3();
var c4 = new Class4();

c1.Numbers = new List<int> {4, 5, 6}; // Works
c1.Numbers.Clear(); // Works

c2.Numbers = new List<int> {4, 5, 6}; // Error
c2.Numbers = c2.Numbers; // Error - you just can't reassign it!
c2.Numbers.Clear(); // Works - you are just calling the Clear method of the existing list object.

c3.Numbers = new ReadOnlyCollection<int> {4, 5, 6}; // Works - the member is not readonly

// ReadOnlyCollection doesn't allow to change it's elements after initializing it.
// It doesn't even have these functions:
c3.Numbers.Clear(); // Error
c3.Numbers.Add(); // Error
c3.Numbers.Remove(2); // Error

c4.Numbers = new ReadOnlyCollection<int> {4, 5, 6}; // Error - the member is marked as readonly

// ReadOnlyCollection doesn't allow to change it's elements after initializing it.
// It doesn't even have these functions:
c4.Numbers.Clear(); // Error
c4.Numbers.Add(); // Error
c4.Numbers.Remove(2); // Error

Try the following - remember they need to be able to deselect dates as well:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (ViewState["StartDates"] == null)
        {
            startDates = new ArrayList();
        }
        else
        {
            startDates = ViewState["StartDates"] as ArrayList;
        }
    } 

    protected void StartSelection_Changed(Object sender, EventArgs e)
    {
        startCalendar.SelectedDate = new DateTime(startCalendar.SelectedDate.Year,
            startCalendar.SelectedDate.Month, startCalendar.SelectedDate.Day, 0, 0, 0);

        // c.f. http://www.mikepope.com/blog/AddComment.aspx?blogid=604
        int index = -1;
        index = startDates.IndexOf(startCalendar.SelectedDate);
        if (index >= 0)
        {  
            startDates.RemoveAt(index);
        }
        else
        {
            DateTime dateTime 
                = new DateTime(startCalendar.SelectedDate.Year,
                    startCalendar.SelectedDate.Month, 
                        startCalendar.SelectedDate.Day, 0, 0, 0);

            startDates.Add(dateTime);
        }

        ViewState["StartDates"] = startDates;
        DisplaySelectedDates();
    }


    protected void DisplaySelectedDates()
    {
        startCalendar.SelectedDates.Clear();
        for (int i = 0; i < startDates.Count; i++)
        {
            startCalendar.SelectedDates.Add((DateTime)startDates[i]);
        }
    }

There is a more concise answer to your question:

HOW is the SelectedDates read-only property affected by a local SelectedDatesCollection variable? Why does adding to that local variable affect the Calendar1.SelectedDates property?

SelectedDatesCollection is a reference type; the read-only SelectedDates property returns a reference to an object of that type. The local variable therefore also holds a reference to that object. Because that object is the same object referenced by the CalendarControl, changes to that object are reflected in the CalendarControl.


Another question that arises from this situation:

Can this cause problems when designing a class?

Yes! Many developers are lured into a false sense of security on the false assumption that exposing, for example, a list through a read-only property will prevent consumer code from modifying the list. This is of course not true: exposing private reference-type data through a read-only property allows users of your code to change the object. For example:

class Parent
{
    private readonly List<Child> _children = new List<Child>();
    public List<Child> Children { get { return _children; } }
}

class SomeOtherClass
{
    void EvilParentConsumer()
    {
        Parent a = GetSomeParent();
        Parent b = GetSomeParent();

        //Kidnap all of b's children and give them to a!!!
        a.Children.AddRange(b.Children);
        b.Children.Clear();
    }
}

As Yorye Nathan notes, you can prevent this by exposing the private list as a ReadOnlyCollection . Another way to do that, if your requirements permit, is to expose the collection as an IEnumerable<T> , which exposes no method for adding elements to the collection.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM