Click here to Skip to main content
15,887,464 members
Articles / Programming Languages / C#

The List Trifecta, Part 1

Rate me:
Please Sign up or sign in to vote.
4.97/5 (21 votes)
20 May 2016LGPL321 min read 37.1K   161   40  
The A-list is an all-purpose list, a data structure that can support most standard list operation in O(log n) time and does lots of other stuff, too
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using NUnit.Framework;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Loyc.Utilities;

namespace Loyc.Essentials
{
	[TestFixture]
	public class GoInterfaceTests
	{
		#region TestTheBasics (ensures structs, classes and interfaces can be wrapped)
		// Also tests object method forwarding (ToString, Equals and GetHashCode)

		public class SimpleClass : IDifferentName
		{
			public int Mutate(int x) { return x * x; }
		}
		public struct SimpleStruct
		{
			public SimpleStruct(int multiplier) { _x = multiplier; }
			int _x;
			public int Mutate(int x) { return x * _x; }

			// Test forwarding
			public override string ToString() { return "forwarded!"; }
			public override bool Equals(object obj)
			{
				return obj is SimpleStruct && _x == ((SimpleStruct)obj)._x;
			}
			public override int GetHashCode()
			{
				return _x.GetHashCode();
			}
		}
		public interface ISimple
		{
			int Mutate(int x);
		}
		public interface IDifferentName
		{
			int Mutate(int x);
		}
		public abstract class SimpleBase
		{
			public abstract int Mutate(int x);
		}

		[Test]
		public void TestTheBasics()
		{
			ISimple class1 = GoInterface<ISimple, SimpleClass>.From(new SimpleClass());
			ISimple iface1 = GoInterface<ISimple, IDifferentName>.From(new SimpleClass());
			ISimple struct1 = GoInterface<ISimple, SimpleStruct>.From(new SimpleStruct(10));

			Assert.AreEqual(25, class1.Mutate(5));
			Assert.AreEqual(25, iface1.Mutate(5));
			Assert.AreEqual(70, struct1.Mutate(7));

			SimpleBase class2 = GoInterface<SimpleBase, SimpleClass>.From(new SimpleClass());
			SimpleBase iface2 = GoInterface<SimpleBase, IDifferentName>.From(new SimpleClass());
			SimpleBase struct2 = GoInterface<SimpleBase, SimpleStruct>.From(new SimpleStruct(10));

			for (int i = 0; i < 10; i++)
			{
				Assert.AreEqual(class1.Mutate(i), class2.Mutate(i));
				Assert.AreEqual(iface1.Mutate(i), iface2.Mutate(i));
				Assert.AreEqual(struct1.Mutate(i), struct2.Mutate(i));
			}

			Assert.AreEqual("forwarded!", struct1.ToString());
			Assert.AreEqual("forwarded!", struct2.ToString());
			Assert.AreEqual(struct1.GetHashCode(), struct2.GetHashCode());
			// Note that struct1.Equals(struct2) returns FALSE because struct2 is
			// not "really" a SimpleStruct. We must "unwrap" the right-hand side:
			Assert.That(struct1.Equals(GoInterface.Unwrap(struct2)));
			Assert.That(struct2.Equals(GoInterface.Unwrap(struct1)));
		}
		
		#endregion

		#region TestAdaptation (test implicit parameter coersions)

		public class AdaptationTests1
		{
			public void Passthrough(int i, double d, object o, string s) { }
			public bool Passthrough(out int i, out double d, out object o, out string s) { i = 1; d = 2; o = 3; s = "4"; return true; }
			public bool Enlarge2int(int i, bool signed) { return signed ? i == -1 : i == 0xFFFF; }
			public bool Enlarge2uint(uint i) { return i == 0xFF || i == 0xFFFF; }
			public bool Enlarge2long(long i, bool signed) { return signed ? i == -1 : (i == 0xFFFF || i == 0xFFFFFFFF); }
			public bool Enlarge2float(float one) { return one == 1.0f; }
			public bool Enlarge2double(double one) { return one == 1.0f; }
			public bool Box(object one) { return Convert.ToDouble(one) == 1.0; }
			public bool True() { return true; }
		}
		public class AdaptationTests2
		{
			public void Covariant(out ushort x) { x = 1; }
			public void Covariant(out MemoryStream obj) { obj = new MemoryStream(); }
			public bool Covariant2(out short one) { one = 1; return true; }
			public bool Invariant(string x) { return x == "1"; }
			public bool Invariant2(ref string x) { return x == "1"; }
			public bool Contravariant(object one) { return one.ToString() == "1"; }
			public bool Contravariant(IDisposable obj) { return obj != null; }
			public bool Contravariant2(ref object one) { return one is int && (int)one == 1; }
			public bool Contravariant3(long one) { return one == 1; }
			public int CovariantReturn() { return 1; }
			public bool CovariantReturn2() { return true; }
			public string CovariantReturn3() { return "true"; }
			public sbyte CovariantReturn4() { return 1; }
			public bool DroppedParam() { return true; }
			public bool DroppedParam2(out int x) { x = 1; return true; }
			public bool DroppedParam3(out object x) { x = null; return true; }
			public bool DefaultParam([Optional, DefaultParameterValue(1.0)] double x) { return x == 1.0; }
		}
		public abstract class AdaptationTestBase
		{
			public abstract void Passthrough(int i, double d, object o, string s);
			public abstract bool Passthrough(out int i, out double d, out object o, out string s);
			public abstract bool Enlarge2int(short i, bool @true);
			public abstract bool Enlarge2int(ushort i, bool @false);
			public abstract bool Enlarge2uint(byte i);
			public abstract bool Enlarge2uint(ushort i);
			public abstract bool Enlarge2long(int i, bool @true);
			public abstract bool Enlarge2long(ushort i, bool @false);
			public abstract bool Enlarge2long(uint i, bool @false);
			public abstract bool Enlarge2float(ushort one);
			public abstract bool Enlarge2double(int one);
			public abstract bool Box(int one);
			public abstract bool Box(double one);
			[GoAlias("True")]
			public abstract bool Renamed();

			public abstract void Covariant(out int x);
			public abstract void Covariant(out IDisposable obj);
			public abstract bool Covariant2(ref int one);
			public abstract bool Invariant(ref string x);
			public abstract bool Invariant2(string x);
			public abstract bool Contravariant(string one);
			public abstract bool Contravariant(MemoryStream obj);
			public abstract bool Contravariant2(int one);
			public abstract bool Contravariant3(ref int one);
			public abstract void CovariantReturn();
			public abstract object CovariantReturn2();
			public abstract object CovariantReturn3();
			public abstract float CovariantReturn4();
			public abstract bool DroppedParam(int x);
			public abstract bool DroppedParam2();
			public abstract bool DroppedParam3(string x);
			public abstract bool DefaultParam();
		}

		[Test]
		public void TestAdaptation()
		{
			AdaptationTestBase t = GoInterface<AdaptationTestBase, AdaptationTests1>.ForceFrom(new AdaptationTests1());

			t.Passthrough(1, 2, 3, "4");
			int i; double d; object o; string s;
			Assert.That(t.Passthrough(out i, out d, out o, out s));
			Assert.AreEqual(i, 1);
			Assert.AreEqual(d, 2);
			Assert.AreEqual(o, 3);
			Assert.AreEqual(s, "4");
			Assert.That(t.Enlarge2int((short)-1, true));
			Assert.That(t.Enlarge2int((ushort)0xFFFF, false));
			Assert.That(t.Enlarge2uint((byte)0xFF));
			Assert.That(t.Enlarge2uint((ushort)0xFFFF));
			Assert.That(t.Enlarge2long(-1, true));
			Assert.That(t.Enlarge2long((ushort)0xFFFF, false));
			Assert.That(t.Enlarge2long((uint)0xFFFFFFFF, false));
			Assert.That(t.Enlarge2float(1));
			Assert.That(t.Enlarge2double(1));
			Assert.That(t.Box((int)1));
			Assert.That(t.Box(1.0));
			Assert.That(t.Renamed());

			t = GoInterface<AdaptationTestBase, AdaptationTests2>.ForceFrom(new AdaptationTests2());

			t.Covariant(out i);
			Assert.That(i == 1);
			IDisposable idisp;
			t.Covariant(out idisp);
			Assert.That(idisp is MemoryStream);
			i = 0;
			Assert.That(t.Covariant2(ref i));
			Assert.AreEqual(1, i);
			s = "1";
			Assert.That(t.Invariant(ref s));
			Assert.That(t.Invariant2(s));
			Assert.That(t.Contravariant("1"));
			Assert.That(t.Contravariant(new MemoryStream()));
			Assert.That(t.Contravariant2(1));
			i = 1;
			Assert.That(t.Contravariant3(ref i));
			t.CovariantReturn();
			Assert.That(t.CovariantReturn2() is bool);
			Assert.That(t.CovariantReturn3().ToString() == "true");
			Assert.AreEqual(t.CovariantReturn4(), 1.0f);
			Assert.That(t.DroppedParam(1));
			Assert.That(t.DroppedParam2());
			Assert.That(t.DroppedParam3("1"));
			Assert.That(t.DefaultParam());
		}
		
		#endregion

		#region Overloading tests (makes sure the correct overloads are chosen)
		
		public class OverloadingTest
		{
			// Note: most methods in IOverloadingTest uses "int" parameters.
			// Caller is expected to pass arguments of 1, 2, etc.

			// The second overload matches in these cases because interface has 2+ args
			public bool MoreMatchingArgsAreBetter(int x) { return false; }
			public bool MoreMatchingArgsAreBetter(int x, int y) { return x == 1 && y == 2; }
			public bool MoreMatchingArgsAreBetter2(int x) { return false; }
			public bool MoreMatchingArgsAreBetter2(long x, long y) { return x == 1 && y == 2; }

			// Obviously the 'short' overloads should not be called
			public bool MinimumSizeRequired(short x) { return false; }
			public bool MinimumSizeRequired(long x) { return x == 1; }
			public bool MinimumSizeRequired2(ushort x) { return false; }
			public bool MinimumSizeRequired2(long x) { return x == 1; }

			// Value types are boxed to object
			public bool Boxing(object x) { return x is int && (int)x == 1; }
			public bool Boxing(string x) { return false; }
			public bool Boxing(byte x) { return false; }

			// Interface just takes an int; ensure nothing else distracts our code
			public bool ManyOverloads(int x) { return x == 1; }
			public bool ManyOverloads(int x, out int y) { y = 2; return false; }
			public bool ManyOverloads(uint x, out string y) { y = "2"; return false; }
			public bool ManyOverloads(long x) { return false; }
			public bool ManyOverloads(object x) { return false; }
			public bool ManyOverloads(ref int x) { return false; }
			public bool ManyOverloads(double x) { return false; }
			public bool ManyOverloads(char x) { return false; }

			// Signed argument => signed overload
			public bool SignedIsBetter(int x) { return x == 1; }
			public bool SignedIsBetter(uint x) { return false; }
			public bool SignedIsBetter2(uint x) { return false; }
			public bool SignedIsBetter2(long x) { return x == 1; }

			// Unsigned argument => unsigned overload
			public bool UnsignedIsBetter(int x) { return false; }
			public bool UnsignedIsBetter(uint x) { return x == 1; }
			public bool UnsignedIsBetter2(ulong x) { return x == 1; }
			public bool UnsignedIsBetter2(int x) { return false; }

			// Implicit int or float conversion is better than boxing
			public bool IntIsBetter(long x) { return x == 1; }
			public bool IntIsBetter(object x) { return false; }
			public bool FloatIsBetter(double x) { return x == 1.0; }
			public bool FloatIsBetter(object x) { return false; }
			public bool FloatIsBetter2(object x) { return false; }
			public bool FloatIsBetter2(double x) { return x == 1.0; }

			// "out" args in target are not required in the interface, and input 
			// arguments in the interface can be omitted when calling the target,
			// provided that missing arguments are at the end of the argument 
			// list. A missing input and output argument can even occur at the 
			// same time.
			public bool MissingOutput(int x, int y) { return false; }
			public bool MissingOutput(int x, out int y, out int z) { y = 2; z = 3; return x == 1; }
			public bool MissingOutput2(out int x) { x = 1; return true; }
			public bool MissingOutput2(int x) { return false; }
			public bool MissingInput(int x) { return false; }
			public bool MissingInput(int x, int y) { return x == 1 && y == 2; }
			public bool MissingInput2() { return true; }
			public bool MissingInputAndOutput(int x, out int y) { y = 2; return x == 1; }
			public bool MissingInputAndOutput(object x, out int y) { y = 2; return false; }

			// Optional parameters are supplied automatically, and they can occur 
			// at the same time as an input parameter in the interface is missing 
			// from the target.
			public bool OptionalParam(int x) { return false; }
			public bool OptionalParam(int x, int y, [DefaultParameterValue(3)] int z) { return x == 1 && y == 2 && z == 3; }
			public bool OptionalParam2(int x) { return false; }
			public bool OptionalParam2(int x, int y, [DefaultParameterValue("3")] string z) { return x == 1 && y == 2 && z == "3"; }

			// The "ref" status of an argument can mismatch, but GoInterface should
			// select an overload without a mismatch whenever possible. However, if
			// the ref-matched overload is incompatible, the ref-unmatched overload 
			// should be chosen instead (as in cases 4 and 5).
			public bool RefMismatch(ref int x) { return false; }
			public bool RefMismatch(int x) { return x == 1; }
			public bool RefMismatch2(ref int x) { return false; }
			public bool RefMismatch2(int x, [DefaultParameterValue(2)] int y) { return x == 1 && y == 2; }
			public bool RefMismatch3(ref int x) { return x == 1; }
			public bool RefMismatch3(int x) { return false; }
			public bool RefMismatch4(ref long x) { return false; }
			public bool RefMismatch4(ref short x) { return false; }
			public bool RefMismatch4(int x) { return true; }
			public bool RefMismatch5(long x) { return true; }
			public bool RefMismatch5(ref short x) { return false; }
			public bool RefMismatch5(ref long x) { return false; }

			// When there are multiple matching overloads, GoInterface should pick 
			// the one that is "nearest" in the type hierarchy.
			public bool Contravariance(object x) { return x.ToString() == "1"; }
			public bool Contravariance2(Stream s) { return true; }
			public bool Contravariance2(IDisposable s) { return false; }
			public bool Contravariance2(object s) { return false; }
			public bool Contravariance3(IDisposable s) { return true; }
			public bool Contravariance3(object s) { return false; }

			// For out parameters, the choice of which parameter type is "better" 
			// works in reverse.
			public bool Covariance(out short x) { x = 1; return true; }
			public bool Covariance(out long x) { x = 1; return false; }
			public bool Covariance2(out Stream x) { x = null; return true; }
			public bool Covariance2(out MemoryStream x) { x = null; return false; }

			// Return-type covariance works very much like 'out' covariance, with 
			// the added caveat that the in interface, the return value can be 
			// 'void'. Covariance is 'better' than a missing parameter.
			public int ReturnCovariance() { return 1; } // preferred
			public object ReturnCovariance(int x) { return null; }
			public byte ReturnCovariance2() { return 1; } // preferred
			public int ReturnCovariance2(int x) { return 0; }
			public int ReturnCovariance3(out bool correct) { correct = true; return 1; } // preferred
			public void ReturnCovariance3(out bool correct, out int x) { x = 1; correct = false; }

			// GoInterface can disambiguate based on return type alone.
			public bool ChooseTheMatchingReturn(int x, out int y) { y = 2; return x == 1; }
			public string ChooseTheMatchingReturn(int x) { return null; }

			protected bool IgnoreNonPublic(int x) { return false; }
			public bool IgnoreNonPublic(object x) { return x is int && (int)x == 1; }
		}
		public interface IOverloadingTest
		{
			bool MoreMatchingArgsAreBetter(int x, int y);
			bool MoreMatchingArgsAreBetter2(int x, int y, int z);
			bool MinimumSizeRequired(int x);
			bool MinimumSizeRequired2(uint x);
			bool SignedIsBetter(int x);
			bool SignedIsBetter2(int x);
			bool UnsignedIsBetter(uint x);
			bool UnsignedIsBetter2(uint x);
			bool IntIsBetter(int x);
			bool FloatIsBetter(int x);
			bool FloatIsBetter2(float x);
			bool MissingOutput(int x);
			bool MissingOutput2();
			bool MissingInput(int x, int y, int z);
			bool MissingInput2(int x, int y);
			bool MissingInputAndOutput(int x, int y, int z);
			bool OptionalParam(int x, int y);
			bool OptionalParam2(int x, int y, int z);
			bool RefMismatch(int x);
			bool RefMismatch2(int x);
			bool RefMismatch3(ref int x);
			bool RefMismatch4(ref int x);
			bool RefMismatch5(int x);
			bool Contravariance(string x);
			bool Contravariance2(MemoryStream x);
			bool Contravariance3(MemoryStream x);
			bool Covariance(out int x);
			bool Covariance2(out object x);
			object ReturnCovariance();
			float ReturnCovariance2();
			void ReturnCovariance3(out bool correct);
			bool ChooseTheMatchingReturn(int x);
			bool IgnoreNonPublic(int x);
		}

		[Test]
		public void TestOverloading()
		{
			bool correct;
			int x = 1;
			object o;
			IOverloadingTest i = GoInterface<IOverloadingTest>.ForceFrom(new OverloadingTest());

			Assert.That(i.MoreMatchingArgsAreBetter(1, 2));
			Assert.That(i.MoreMatchingArgsAreBetter2(1, 2, 3));
			Assert.That(i.MinimumSizeRequired(1));
			Assert.That(i.MinimumSizeRequired2(1));
			Assert.That(i.SignedIsBetter(1));
			Assert.That(i.SignedIsBetter2(1));
			Assert.That(i.UnsignedIsBetter(1));
			Assert.That(i.UnsignedIsBetter2(1));
			Assert.That(i.IntIsBetter(1));
			Assert.That(i.FloatIsBetter(1));
			Assert.That(i.FloatIsBetter2(1.0f));
			Assert.That(i.MissingOutput(1));
			Assert.That(i.MissingOutput2());
			Assert.That(i.MissingInput(1, 2, 3));
			Assert.That(i.MissingInput2(1, 2));
			Assert.That(i.MissingInputAndOutput(1, 2, 3));
			Assert.That(i.OptionalParam(1, 2));
			Assert.That(i.OptionalParam2(1, 2, 3));
			Assert.That(i.RefMismatch(1));
			Assert.That(i.RefMismatch2(1));
			Assert.That(i.RefMismatch3(ref x));
			Assert.That(i.RefMismatch4(ref x));
			Assert.That(i.RefMismatch5(1));
			Assert.That(i.Contravariance("1"));
			Assert.That(i.Contravariance2(new MemoryStream()));
			Assert.That(i.Contravariance3(new MemoryStream()));
			Assert.That(i.Covariance(out x));
			Assert.That(i.Covariance2(out o));
			Assert.That(i.ReturnCovariance() is int && (int)i.ReturnCovariance() == 1);
			Assert.That(i.ReturnCovariance2() == 1.0f);
			i.ReturnCovariance3(out correct);
			Assert.That(correct);
			Assert.That(i.ChooseTheMatchingReturn(1));
			Assert.That(i.IgnoreNonPublic(1));
		}
		
		#endregion

		#region AliasesAndProperties (ensures properties can be wrapped and renamed)

		public class MyCollection
		{
			List<object> l = new List<object>();
			
			public void Insert(object obj)
			{
				l.Add(obj);
			}
			public int Size
			{
				get { return l.Count; }
			}
			public object GetAt(int i)
			{
				return l[i];
			}
		}
		public interface ISimpleList
		{
			[GoAlias("Insert")]
			void Add(object item);

			int Count
			{
				[GoAlias("get_Size")]
				get;
			}
			object this[int index]
			{
				[GoAlias("GetAt")]
				get;
			}

			int Nonexistent { get; set; }
		}
		[Test]
		public void AliasesAndProperties()
		{
			ISimpleList list = GoInterface<ISimpleList>.From(new MyCollection(), CastOptions.AllowUnmatchedMethods);

			list.Add(10);
			Assert.That(list[0].Equals(10));
			Assert.AreEqual(1, list.Count);
		}

		#endregion

		#region InheritanceTest (tests several override scenarios)
		// NOTE: GoInterface cannot wrap explicit interface implementations in the target
		// class! For instance, in this test we are wrapping FooB (and FooA, indirectly).
		// If, instead of having a method Bar, FooA implemented IBar and had an explicit
		// interface implementation IBar.Bar(), GoInterface would NOT find it, because it
		// only looks for "normal" implementations.

		public class FooA
		{
			public virtual string Foo() { return null; }
			public virtual string Bar() { return "Bar"; }
			public virtual string Baz() { return "Baz"; }
		}
		public class FooB : FooA
		{
			public override string Foo() { return "Foo"; }
		}

		public interface IBar
		{
			string Bar();
		}
		public interface IBar2
		{
			string Bar();
		}
		public interface IBar3 : IBar, IBar2
		{
			// The CLR considers IBar3 to contain two separate Bar methods!
			// Currently, GoInterface generates two identical Bar() methods,
			// For some reason the CLR doesn't seem to mind, even though 
			// neither of them is an "explicit" interface implementation.
		}
		public interface IBaz : IBar3
		{
			string Baz();

			// These are red herrings to make sure GoInterface does not override 
			// methods that have already been given an implementation.
			string Baz(int goInterfaceShouldIgnoreThisOne);
			string Baz(bool andThisOneTooBecauseTheyAreNotAbstract);
		}
		public abstract class BazBase : IBaz
		{
			public abstract string Bar(); // overrides both?
			public abstract string Baz();
			public abstract string Baz(int i);
			string IBaz.Baz(bool b) { return "Baz"; }
		}
		public abstract class FooBase : BazBase
		{
			public abstract string Foo();
			public override string Baz(int i) { return "Baz"; }
		}

		[Test]
		public void InheritanceTest()
		{
			object something = new FooB();
			FooBase foo = GoInterface<FooBase>.From(something);
			Assert.That(foo.Foo() == "Foo");
			Assert.That(foo.Bar() == "Bar");
			Assert.That(foo.Baz(1) == "Baz");
			Assert.That(((IBaz)foo).Baz(false) == "Baz");
			Assert.That(foo.Baz() == "Baz");

			something = new FooB();
			IBaz baz = GoInterface<IBaz>.ForceFrom(something);
			Assert.That(((IBar)baz).Bar() == "Bar");
			Assert.That(((IBar2)baz).Bar() == "Bar");
			Assert.That(baz.Baz() == "Baz");
		}

		#endregion

		#region ISimpleSource (tests automatic explicit interface implementation)
		// This test also shows that GoInterface can wrap stuff in a "base 
		// interface" that is still abstract.

		public interface ISimpleSource<T> : IEnumerable<T>
		{
			int Count { get; }
		}

		[Test]
		public void ISimpleSourceTest()
		{
			ISimpleSource<string> list = GoInterface<ISimpleSource<string>>.From(new List<string>());
			Assert.AreEqual(0, list.Count);
			Assert.AreEqual(false, list.GetEnumerator().MoveNext());
		}

		#endregion

		#region TestAmbiguity (ensures that ambiguous overloads are detected and not called)

		public class Ambig
		{
			public void Strings(object a, string b) { }
			public void Strings(string a, object b) { }
			public void RefMismatch(ref int a, int b) { }
			public void RefMismatch(int a, ref int b) { }
			public void RefMismatch2(int a, int b, out int c, out int d) { c = 3; d = 4; }
			public void RefMismatch2(out int a, ref int b) { a = 1; b = 2; }
			public void AmbigLarger(uint a) { }
			public void AmbigLarger(float a) { }
		}
		public interface IAmbig
		{
			void Strings(string a, string b);
			void RefMismatch(int a, int b);
			void RefMismatch2(ref int a, int b);
			void AmbigLarger(byte a);
		}
		[Test]
		public void TestAmbiguity()
		{
			IAmbig wrapped;
			try {
				wrapped = GoInterface<IAmbig>.From(new Ambig());
			}
			catch (InvalidCastException e)
			{
				if (e.Message.Contains("4 "))
					return; // 4 ambiguous methods, just as expected
			}
			
			wrapped = GoInterface<IAmbig>.ForceFrom(new Ambig());

			int a = 0;
			Assert.Throws<MissingMethodException>(delegate() { wrapped.Strings("1", "2"); });
			Assert.Throws<MissingMethodException>(delegate() { wrapped.RefMismatch(1, 2); });
			Assert.Throws<MissingMethodException>(delegate() { wrapped.RefMismatch2(ref a, 2); });
			Assert.Throws<MissingMethodException>(delegate() { wrapped.AmbigLarger(1); });
		}

		private void AssertThrows<Type>(TestDelegate @delegate) where Type:Exception
		{
			try {
				@delegate();
				Assert.Fail("Delegate did not throw '{0}' as expected.", typeof(Type).Name);
			} catch (Type) { }
		}

		#endregion
	
		#region TestDecorator (demonstrates how to use GoInterface to help make a decorator)

		// See GoDecoratorFieldAttribute's documentation for more explanation
		public abstract class ReverseView<T> : IList<T>
		{
			[GoDecoratorField]
			protected IList<T> _list;

			protected ReverseView() { Debug.Assert(_list != null); }

			public static ReverseView<T> From(IList<T> list)
			{
				return GoInterface<ReverseView<T>, IList<T>>.From(list);
			}

			public int IndexOf(T item)
			{ 
				int i = _list.IndexOf(item); 
				return i == -1 ? -1 : Count - 1 - i;
			}
			public void Insert(int index, T item)
			{
				_list.Insert(Count - index, item);
			}
			public void RemoveAt(int index)
			{
				_list.RemoveAt(Count - 1 - index);
			}

			public T this[int index]
			{
				get { return _list[Count - 1 - index]; }
				set { _list[Count - 1 - index] = value; }
			}

			public abstract void Add(T item);
			public abstract void Clear();
			public abstract bool Contains(T item);
			public abstract void CopyTo(T[] array, int arrayIndex);
			public abstract int Count { get; }
			public abstract bool IsReadOnly { get; }
			public abstract bool Remove(T item);
			public abstract IEnumerator<T> GetEnumerator();
			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return GetEnumerator();
			}
		}

		[Test]
		public void TestDecorator()
		{
			var fwd = new List<int>();
			var rev = ReverseView<int>.From(fwd);
			rev.Add(1);
			Assert.AreEqual(1, fwd[0]);
			rev.Add(2);
			Assert.AreEqual(fwd.Count, rev.Count);
			Assert.AreEqual(fwd[0], rev[1]);
			rev.Insert(2, 3);
			Assert.AreEqual(3, fwd[0]);
			Assert.AreEqual(2, rev.IndexOf(3));
		}

		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer None
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, the Enhanced C# programming language (in progress), the parser generator LLLPG, and LES, a syntax to help you start building programming languages, DSLs or build systems.

My overall focus is on the Language of your choice (Loyc) initiative, which is about investigating ways to improve interoperability between programming languages and putting more power in the hands of developers. I'm also seeking employment.

Comments and Discussions