[.net] Immutable objects in c#

Started by
17 comments, last by Cryogenic 16 years, 4 months ago
'const' objects in Java are often achieved by creating an Immutable interface and having the original type implement it. Isn't this possible in C# too?
Advertisement
Quote:Original post by OrangyTang
'const' objects in Java are often achieved by creating an Immutable interface and having the original type implement it. Isn't this possible in C# too?


Yeah, though you pretty much need to re-implement the type as a new type (which makes many things infeasible) to do that.
Quote:Original post by Telastyn
Quote:Original post by OrangyTang
'const' objects in Java are often achieved by creating an Immutable interface and having the original type implement it. Isn't this possible in C# too?


Yeah, though you pretty much need to re-implement the type as a new type (which makes many things infeasible) to do that.


I'm not sure I follow you.

public interface ImmutablePoint{  public float getX();  public float getY();}public class Point implements ImmutablePoint{  public float x, y;  public float getX() { return x; }  public float getY() { return y; }}main(){  Point mutable = new Point();  mutable.x = 10; // fine  ImmutablePoint immutable = mutable; // 'const' Point  immutable.x = 10; // compile error}


Verbose, but it gets the job done. What kind of things does it make infeasible?
Quote:
What kind of things does it make infeasible?


The problem is that Point already exists in the standard library. Other parts of the library want to use a Point. Not some new weird Point, but a Point. Which leaves you two options, some sort of implicit conversion which is usually possible, but adds an allocation almost every time you use the class; or inheriting directly from the type, which is 50-50 because a lot of classes are sealed (final) or not structured in a way to be inheritable.


A List for example doesn't have any of its methods listed as virtual, so plan B is right out. Plan A would require a new list copy created every time the conversion occurred (since the user could modify any cached version you give them).
Quote:Original post by Telastyn
Quote:
What kind of things does it make infeasible?


The problem is that Point already exists in the standard library. Other parts of the library want to use a Point. Not some new weird Point, but a Point. Which leaves you two options, some sort of implicit conversion which is usually possible, but adds an allocation almost every time you use the class; or inheriting directly from the type, which is 50-50 because a lot of classes are sealed (final) or not structured in a way to be inheritable.


A List for example doesn't have any of its methods listed as virtual, so plan B is right out. Plan A would require a new list copy created every time the conversion occurred (since the user could modify any cached version you give them).


Ah I see. I usually only have to do this for my own types, so I don't run into those problems. YMMV.
Quote:Original post by OrangyTang
Ah I see. I usually only have to do this for my own types, so I don't run into those problems. YMMV.


Oh yeah, for your own types it's usually not a big deal (unless what you're returning is an event you want to restrict access to...)

Here's how you create an immutable (readonly) list :

using System;using System.Collections.Generic;namespace X {	class Program {		static void Main(string[] args) {			ImmutableList<int> l = new ImmutableList<int>();			// This can't be called from other consumer assembly			l.list.Add(1);			l.list.Add(2);			// these lines not throw an exception			Console.WriteLine(l[0]);			Console.WriteLine(l[1]);			Console.WriteLine(l.Count);			Console.WriteLine(l.IndexOf(2));			//The list can be enumerated			foreach (int i in l) {				Console.WriteLine(i);			}			// the next lines will throw an exception			try { l.Add(3); } catch (Exception ex) { Console.WriteLine(ex.Message); }			try { l[1] = 4; } catch (Exception ex) { Console.WriteLine(ex.Message); }			try { l.Clear(); } catch (Exception ex) { Console.WriteLine(ex.Message); }		}	}	public class ImmutableList<T> : IList<T>{		internal List<T> list = new List<T>();		public ImmutableList() {			list = new List<T>();		}		#region IList<T> Members		public int IndexOf(T item) {			return list.IndexOf(item);		}		public void Insert(int index, T item) {			throw new NotSupportedException("Operation not supported on immutable type");		}		public void RemoveAt(int index) {			throw new NotSupportedException("Operation not supported on immutable type");		}		public T this[int index] {			get {				return list[index];			}			set {				throw new NotSupportedException("Operation not supported on immutable type");			}		}		#endregion		#region ICollection<T> Members		public void Add(T item) {			throw new NotSupportedException("Operation not supported on immutable type");		}		public void Clear() {			throw new NotSupportedException("Operation not supported on immutable type");		}		public bool Contains(T item) {			return list.Contains(item);		}		public void CopyTo(T[] array, int arrayIndex) {			list.CopyTo(array, arrayIndex);		}		public int Count {			get { return list.Count; }		}		public bool IsReadOnly {			get { return true; }		}		public bool Remove(T item) {			throw new NotSupportedException("Operation not supported on immutable type");		}		#endregion		#region IEnumerable<T> Members		public IEnumerator<T> GetEnumerator() {			return list.GetEnumerator();		}		#endregion		#region IEnumerable Members		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {			return list.GetEnumerator();		}		#endregion	}}
Quote:
Here's how you create an immutable (readonly) list :

There is already a ReadOnlyCollection<T> that does this, actually. It's in System.Collections.Generic.ObjectModel.

Soth, as per above, your latest example can be written as:
class Zip{  List<int> lst = new List<int>();  public ReadOnlyCollection<int> List  {    get    {      return (new ReadOnlyCollection<int>(lst));    }  }}static void Main(string[] args){  Zip z = new Zip();    // This line will no longer compile.  z.List.Add(99);}
Quote:Original post by jpetrie
Quote:
Here's how you create an immutable (readonly) list :

There is already a ReadOnlyCollection<T> that does this, actually. It's in System.Collections.Generic.ObjectModel.

Soth, as per above, your latest example can be written as:
class Zip{  List<int> lst = new List<int>();  public ReadOnlyCollection<int> List  {    get    {      return (new ReadOnlyCollection<int>(lst));    }  }}static void Main(string[] args){  Zip z = new Zip();    // This line will no longer compile.  z.List.Add(99);}


This example creates a new readlonly list and copies all elements on every property access.
The better way is to create a internal ReadOnlyCollection<T> and return an ICollection<T> but the the standard ReadOnlyCollection<T> is not appropriate for all scenarios as the collection is entirely readonly and in my example the collection is readonly only for public consumers of the assembly.

This topic is closed to new replies.

Advertisement