-
-
Save michaelbartnett/5652076 to your computer and use it in GitHub Desktop.
This is free and unencumbered software released into the public domain. | |
Anyone is free to copy, modify, publish, use, compile, sell, or | |
distribute this software, either in source code form or as a compiled | |
binary, for any purpose, commercial or non-commercial, and by any | |
means. | |
In jurisdictions that recognize copyright laws, the author or authors | |
of this software dedicate any and all copyright interest in the | |
software to the public domain. We make this dedication for the benefit | |
of the public at large and to the detriment of our heirs and | |
successors. We intend this dedication to be an overt act of | |
relinquishment in perpetuity of all present and future rights to this | |
software under copyright law. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
OTHER DEALINGS IN THE SOFTWARE. | |
For more information, please refer to <http://unlicense.org> |
// ---------------------------------------------------------------------------- | |
// Tuple structs for use in .NET Not-Quite-3.5 (e.g. Unity3D). | |
// | |
// Used Chapter 3 in http://functional-programming.net/ as a starting point. | |
// | |
// Note: .NET 4.0 Tuples are immutable classes so they're *slightly* different. | |
// ---------------------------------------------------------------------------- | |
using System; | |
namespace Eppy | |
{ | |
/// <summary> | |
/// Utility class that simplifies cration of tuples by using | |
/// method calls instead of constructor calls | |
/// </summary> | |
public static class Tuple | |
{ | |
/// <summary> | |
/// Creates a new tuple value with the specified elements. The method | |
/// can be used without specifying the generic parameters, because C# | |
/// compiler can usually infer the actual types. | |
/// </summary> | |
/// <param name="item1">First element of the tuple</param> | |
/// <param name="second">Second element of the tuple</param> | |
/// <returns>A newly created tuple</returns> | |
public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 second) | |
{ | |
return new Tuple<T1, T2>(item1, second); | |
} | |
/// <summary> | |
/// Creates a new tuple value with the specified elements. The method | |
/// can be used without specifying the generic parameters, because C# | |
/// compiler can usually infer the actual types. | |
/// </summary> | |
/// <param name="item1">First element of the tuple</param> | |
/// <param name="second">Second element of the tuple</param> | |
/// <param name="third">Third element of the tuple</param> | |
/// <returns>A newly created tuple</returns> | |
public static Tuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 second, T3 third) | |
{ | |
return new Tuple<T1, T2, T3>(item1, second, third); | |
} | |
/// <summary> | |
/// Creates a new tuple value with the specified elements. The method | |
/// can be used without specifying the generic parameters, because C# | |
/// compiler can usually infer the actual types. | |
/// </summary> | |
/// <param name="item1">First element of the tuple</param> | |
/// <param name="second">Second element of the tuple</param> | |
/// <param name="third">Third element of the tuple</param> | |
/// <param name="fourth">Fourth element of the tuple</param> | |
/// <returns>A newly created tuple</returns> | |
public static Tuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 item1, T2 second, T3 third, T4 fourth) | |
{ | |
return new Tuple<T1, T2, T3, T4>(item1, second, third, fourth); | |
} | |
/// <summary> | |
/// Extension method that provides a concise utility for unpacking | |
/// tuple components into specific out parameters. | |
/// </summary> | |
/// <param name="tuple">the tuple to unpack from</param> | |
/// <param name="ref1">the out parameter that will be assigned tuple.Item1</param> | |
/// <param name="ref2">the out parameter that will be assigned tuple.Item2</param> | |
public static void Unpack<T1, T2>(this Tuple<T1, T2> tuple, out T1 ref1, out T2 ref2) | |
{ | |
ref1 = tuple.Item1; | |
ref2 = tuple.Item2; | |
} | |
/// <summary> | |
/// Extension method that provides a concise utility for unpacking | |
/// tuple components into specific out parameters. | |
/// </summary> | |
/// <param name="tuple">the tuple to unpack from</param> | |
/// <param name="ref1">the out parameter that will be assigned tuple.Item1</param> | |
/// <param name="ref2">the out parameter that will be assigned tuple.Item2</param> | |
/// <param name="ref3">the out parameter that will be assigned tuple.Item3</param> | |
public static void Unpack<T1, T2, T3>(this Tuple<T1, T2, T3> tuple, out T1 ref1, out T2 ref2, T3 ref3) | |
{ | |
ref1 = tuple.Item1; | |
ref2 = tuple.Item2; | |
ref3 = tuple.Item3; | |
} | |
/// <summary> | |
/// Extension method that provides a concise utility for unpacking | |
/// tuple components into specific out parameters. | |
/// </summary> | |
/// <param name="tuple">the tuple to unpack from</param> | |
/// <param name="ref1">the out parameter that will be assigned tuple.Item1</param> | |
/// <param name="ref2">the out parameter that will be assigned tuple.Item2</param> | |
/// <param name="ref3">the out parameter that will be assigned tuple.Item3</param> | |
/// <param name="ref4">the out parameter that will be assigned tuple.Item4</param> | |
public static void Unpack<T1, T2, T3, T4>(this Tuple<T1, T2, T3, T4> tuple, out T1 ref1, out T2 ref2, T3 ref3, T4 ref4) | |
{ | |
ref1 = tuple.Item1; | |
ref2 = tuple.Item2; | |
ref3 = tuple.Item3; | |
ref4 = tuple.Item4; | |
} | |
} | |
} |
// ---------------------------------------------------------------------------- | |
// Tuple structs for use in .NET Not-Quite-3.5 (e.g. Unity3D). | |
// | |
// Used Chapter 3 in http://functional-programming.net/ as a starting point. | |
// | |
// Note: .NET 4.0 Tuples are immutable classes so they're *slightly* different. | |
// ---------------------------------------------------------------------------- | |
using System; | |
namespace Eppy | |
{ | |
/// <summary> | |
/// Represents a functional tuple that can be used to store | |
/// two values of different types inside one object. | |
/// </summary> | |
/// <typeparam name="T1">The type of the first element</typeparam> | |
/// <typeparam name="T2">The type of the second element</typeparam> | |
public sealed class Tuple<T1, T2> | |
{ | |
private readonly T1 item1; | |
private readonly T2 item2; | |
/// <summary> | |
/// Retyurns the first element of the tuple | |
/// </summary> | |
public T1 Item1 | |
{ | |
get { return item1; } | |
} | |
/// <summary> | |
/// Returns the second element of the tuple | |
/// </summary> | |
public T2 Item2 | |
{ | |
get { return item2; } | |
} | |
/// <summary> | |
/// Create a new tuple value | |
/// </summary> | |
/// <param name="item1">First element of the tuple</param> | |
/// <param name="second">Second element of the tuple</param> | |
public Tuple(T1 item1, T2 item2) | |
{ | |
this.item1 = item1; | |
this.item2 = item2; | |
} | |
public override string ToString() | |
{ | |
return string.Format("Tuple({0}, {1})", Item1, Item2); | |
} | |
public override int GetHashCode() | |
{ | |
int hash = 17; | |
hash = hash * 23 + (item1 == null ? 0 : item1.GetHashCode()); | |
hash = hash * 23 + (item2 == null ? 0 : item2.GetHashCode()); | |
return hash; | |
} | |
public override bool Equals(object o) | |
{ | |
if (!(o is Tuple<T1, T2>)) { | |
return false; | |
} | |
var other = (Tuple<T1, T2>) o; | |
return this == other; | |
} | |
public bool Equals(Tuple<T1, T2> other) | |
{ | |
return this == other; | |
} | |
public static bool operator==(Tuple<T1, T2> a, Tuple<T1, T2> b) | |
{ | |
if (object.ReferenceEquals(a, null)) { | |
return object.ReferenceEquals(b, null); | |
} | |
if (a.item1 == null && b.item1 != null) return false; | |
if (a.item2 == null && b.item2 != null) return false; | |
return | |
a.item1.Equals(b.item1) && | |
a.item2.Equals(b.item2); | |
} | |
public static bool operator!=(Tuple<T1, T2> a, Tuple<T1, T2> b) | |
{ | |
return !(a == b); | |
} | |
public void Unpack(Action<T1, T2> unpackerDelegate) | |
{ | |
unpackerDelegate(Item1, Item2); | |
} | |
} | |
} |
// ---------------------------------------------------------------------------- | |
// Tuple structs for use in .NET Not-Quite-3.5 (e.g. Unity3D). | |
// | |
// Used Chapter 3 in http://functional-programming.net/ as a starting point. | |
// | |
// Note: .NET 4.0 Tuples are immutable classes so they're *slightly* different. | |
// ---------------------------------------------------------------------------- | |
using System; | |
namespace Eppy | |
{ | |
/// <summary> | |
/// Represents a functional tuple that can be used to store | |
/// two values of different types inside one object. | |
/// </summary> | |
/// <typeparam name="T1">The type of the first element</typeparam> | |
/// <typeparam name="T2">The type of the second element</typeparam> | |
/// <typeparam name="T3">The type of the third element</typeparam> | |
public sealed class Tuple<T1, T2, T3> | |
{ | |
private readonly T1 item1; | |
private readonly T2 item2; | |
private readonly T3 item3; | |
/// <summary> | |
/// Retyurns the first element of the tuple | |
/// </summary> | |
public T1 Item1 | |
{ | |
get { return item1; } | |
} | |
/// <summary> | |
/// Returns the second element of the tuple | |
/// </summary> | |
public T2 Item2 | |
{ | |
get { return item2; } | |
} | |
/// <summary> | |
/// Returns the second element of the tuple | |
/// </summary> | |
public T3 Item3 | |
{ | |
get { return item3; } | |
} | |
/// <summary> | |
/// Create a new tuple value | |
/// </summary> | |
/// <param name="item1">First element of the tuple</param> | |
/// <param name="second">Second element of the tuple</param> | |
/// <param name="third">Third element of the tuple</param> | |
public Tuple(T1 item1, T2 item2, T3 item3) | |
{ | |
this.item1 = item1; | |
this.item2 = item2; | |
this.item3 = item3; | |
} | |
public override int GetHashCode() | |
{ | |
int hash = 17; | |
hash = hash * 23 + (item1 == null ? 0 : item1.GetHashCode()); | |
hash = hash * 23 + (item2 == null ? 0 : item2.GetHashCode()); | |
hash = hash * 23 + (item3 == null ? 0 : item3.GetHashCode()); | |
return hash; | |
} | |
public override bool Equals(object o) | |
{ | |
if (!(o is Tuple<T1, T2, T3>)) { | |
return false; | |
} | |
var other = (Tuple<T1, T2, T3>)o; | |
return this == other; | |
} | |
public static bool operator==(Tuple<T1, T2, T3> a, Tuple<T1, T2, T3> b) | |
{ | |
if (object.ReferenceEquals(a, null)) { | |
return object.ReferenceEquals(b, null); | |
} | |
if (a.item1 == null && b.item1 != null) return false; | |
if (a.item2 == null && b.item2 != null) return false; | |
if (a.item3 == null && b.item3 != null) return false; | |
return | |
a.item1.Equals(b.item1) && | |
a.item2.Equals(b.item2) && | |
a.item3.Equals(b.item3); | |
} | |
public static bool operator!=(Tuple<T1, T2, T3> a, Tuple<T1, T2, T3> b) | |
{ | |
return !(a == b); | |
} | |
public void Unpack(Action<T1, T2, T3> unpackerDelegate) | |
{ | |
unpackerDelegate(Item1, Item2, Item3); | |
} | |
} | |
} |
// ---------------------------------------------------------------------------- | |
// Tuple structs for use in .NET Not-Quite-3.5 (e.g. Unity3D). | |
// | |
// Used Chapter 3 in http://functional-programming.net/ as a starting point. | |
// | |
// Note: .NET 4.0 Tuples are immutable classes so they're *slightly* different. | |
// ---------------------------------------------------------------------------- | |
using System; | |
namespace Eppy | |
{ | |
/// <summary> | |
/// Represents a functional tuple that can be used to store | |
/// two values of different types inside one object. | |
/// </summary> | |
/// <typeparam name="T1">The type of the first element</typeparam> | |
/// <typeparam name="T2">The type of the second element</typeparam> | |
/// <typeparam name="T3">The type of the third element</typeparam> | |
/// <typeparam name="T4">The type of the fourth element</typeparam> | |
public sealed class Tuple<T1, T2, T3, T4> | |
{ | |
private readonly T1 item1; | |
private readonly T2 item2; | |
private readonly T3 item3; | |
private readonly T4 item4; | |
/// <summary> | |
/// Retyurns the first element of the tuple | |
/// </summary> | |
public T1 Item1 | |
{ | |
get { return item1; } | |
} | |
/// <summary> | |
/// Returns the second element of the tuple | |
/// </summary> | |
public T2 Item2 | |
{ | |
get { return item2; } | |
} | |
/// <summary> | |
/// Returns the second element of the tuple | |
/// </summary> | |
public T3 Item3 | |
{ | |
get { return item3; } | |
} | |
/// <summary> | |
/// Returns the second element of the tuple | |
/// </summary> | |
public T4 Item4 | |
{ | |
get { return item4; } | |
} | |
/// <summary> | |
/// Create a new tuple value | |
/// </summary> | |
/// <param name="item1">First element of the tuple</param> | |
/// <param name="second">Second element of the tuple</param> | |
/// <param name="third">Third element of the tuple</param> | |
/// <param name="fourth">Fourth element of the tuple</param> | |
public Tuple(T1 item1, T2 item2, T3 item3, T4 item4) | |
{ | |
this.item1 = item1; | |
this.item2 = item2; | |
this.item3 = item3; | |
this.item4 = item4; | |
} | |
public override int GetHashCode() | |
{ | |
int hash = 17; | |
hash = hash * 23 + (item1 == null ? 0 : item1.GetHashCode()); | |
hash = hash * 23 + (item2 == null ? 0 : item2.GetHashCode()); | |
hash = hash * 23 + (item3 == null ? 0 : item3.GetHashCode()); | |
hash = hash * 23 + (item4 == null ? 0 : item4.GetHashCode()); | |
return hash; | |
} | |
public override bool Equals(object o) | |
{ | |
if (o.GetType() != typeof(Tuple<T1, T2, T3, T4>)) { | |
return false; | |
} | |
var other = (Tuple<T1, T2, T3, T4>)o; | |
return this == other; | |
} | |
public static bool operator==(Tuple<T1, T2, T3, T4> a, Tuple<T1, T2, T3, T4> b) | |
{ | |
if (object.ReferenceEquals(a, null)) { | |
return object.ReferenceEquals(b, null); | |
} | |
if (a.item1 == null && b.item1 != null) return false; | |
if (a.item2 == null && b.item2 != null) return false; | |
if (a.item3 == null && b.item3 != null) return false; | |
if (a.item4 == null && b.item4 != null) return false; | |
return | |
a.item1.Equals(b.item1) && | |
a.item2.Equals(b.item2) && | |
a.item3.Equals(b.item3) && | |
a.item4.Equals(b.item4); | |
} | |
public static bool operator!=(Tuple<T1, T2, T3, T4> a, Tuple<T1, T2, T3, T4> b) | |
{ | |
return !(a == b); | |
} | |
public void Unpack(Action<T1, T2, T3, T4> unpackerDelegate) | |
{ | |
unpackerDelegate(Item1, Item2, Item3, Item4); | |
} | |
} | |
} |
I couldn't believe that Unity does not support this. Thanks for saving the day!
This definitely merits a repository
thanks for your work!
I'd remove unused using System.Collections.Generic, and also it would be great to rename Tuple to something different, so if unity will add Tuple in future versions, we won't be confused. Since behaviour of this implementation of tuples is significantly different comparing to System.Tuple it can cause a lot of errors that hard to track down
Thanks for these classes, they are very useful!
One question: Why do you make the tuple-classes sealed?
Standard c#-tuples seem to not be sealed, so you can make stuff like in the second answer to that post: http://stackoverflow.com/questions/1490413/languages-that-allow-named-tuples
(which seems to be quite nice)
Question is of pure curiosity
@Hustenbonbon Purely for defensive programming, to discourage people (future me, in all likelihood 😄) from subclassing and accidentally mucking up the hash/equality implementations I had thought about that subclassing case earlier on (typing Item1, Item2, etc. is annoying!) but decided that if desperately want to name tuple fields, I probably really want to write a struct. Looking forward to the new C# record types to fill that gap though!
@Radagaisus Eh, if I give it a repo, then it also merits a test suite 😆
@JohnPoison Ah good point, dunno why I had those collections.generic imports in there. For the naming thing, I only do C# when working with Unity, so I'd rather these just stay named Tuple for my own copy-pasta convenience. I hope the namespacing helps, and since it's a Gist I kind of assume the workflow here is: copy-paste into your project, rename things, and add or remove helper methods to your preference.
Aren't == operators generally suppose to test for reference equality and Equals methods for value equality?
Edit: Ah ok, so jjcat posted proposed separation of function for those so I suppose this wasn't meant to be like that
Thanks for the classes!
The code for operator== isn't quite correct. Something like
if (myTuple == null)
will throw an exception, because the second parameter ("b") isn't null-checked in the code.
Here's one way to fix it (replaces the ReferenceEquals calls at the top of the method):
bool aIsNull = object.ReferenceEquals(a, null);
bool bIsNull = object.ReferenceEquals(b, null);
if (aIsNull || bIsNull)
{
return aIsNull && bIsNull;
}
I'm sure there are more elegant ways, but this one works and is readable.
Thanks again for the great classes!
Even with kmeboe's fix, this still breaks:
var a = Tuple<string, string>("1", null);
var b = Tuple<string, string>("1", null);
Debug.Log(a == b); // NullReferenceException
My first pass at a solution that fixes all null cases was this:
public static bool operator ==(Tuple<T1, T2> a, Tuple<T1, T2> b)
{
// Handle a or b being null
bool bIsNull = ReferenceEquals(b, null);
// a is null, equality depends on b being null
if (ReferenceEquals(a, null))
return bIsNull;
// a NOT null, b is null, so NOT EQUAL
else if (bIsNull)
return false;
// a.1 is null.
if (ReferenceEquals(a.item1, null))
{
// We only know NOT EQUAL if b.1 is not null
if (!ReferenceEquals(b.item1, null))
return false;
}
// a.1 is not null, so NOT EQUAL if a.1 != b.1
else if (!a.item1.Equals(b.item1))
return false;
// a.2 is null.
if (ReferenceEquals(a.item2, null))
{
// We only know NOT EQUAL if b2 is not null
if (!ReferenceEquals(b.item2, null))
return false;
}
// a.2 is not null, so NOT EQUAL if a.2 != b.2
else if (!a.item2.Equals(b.item2))
return false;
// Everything was equal
return true;
}
However, if we can use object.Equals(obj a, obj b)
then it can be simplified to just:
public static bool operator ==(Tuple<T1, T2> a, Tuple<T1, T2> b)
{
// Handle a or b being null
bool bIsNull = ReferenceEquals(b, null);
// a is null, equaliy depends on b being null
if (ReferenceEquals(a, null))
return bIsNull;
// a NOT null, b is null, so NOT EQUAL
else if (bIsNull)
return false;
// Handle Items
return object.Equals(a.item1, b.item1) && object.Equals(a.item2, b.item2);
}
I'm unsure if there might be a reason we don't want to use object.Equals
for the items. Thoughts?
In any case, I believe either of these solutions handle all null permutations. To verify for yourself, here is a simple test:
static void RunTest()
{
Tuple<string, string>[] list =
{
new Tuple<string, string>("1", "2"),
new Tuple<string, string>("1", null),
new Tuple<string, string>(null, "2"),
new Tuple<string, string>(null, null),
null // just to make sure we are checking for null objects
};
foreach(var lhs in list)
{
// Check for same values, different object
if(lhs != null)
{
DoCompare(lhs, Tuple.Create(lhs.Item1, lhs.Item2));
DoCompare(Tuple.Create(lhs.Item1, lhs.Item2), lhs);
}
// Check all pairings
foreach(var rhs in list)
DoCompare(lhs, rhs);
}
}
static void DoCompare(Tuple<string, string> lhs, Tuple<string, string> rhs)
{
Debug.LogFormat("{0} == {1} : {2}",
lhs == null ? "NULL" : lhs.ToString(),
rhs == null ? "NULL" : rhs.ToString(),
(lhs == rhs)
);
}
It should be noted that this code can be simplified considerably if you don't expect your tuples to contain null values. I personally don't feel comfortable making that assumption.
@michaelbartnett Thanks for the tuples! They've been exceptionally useful.
I believe there is a bug in Tuple4 Equals.
original:
if (o.GetType() != typeof(NamedTuple<T1, T2, T3, T4>)) {
return false;
}
fix:
if (!(o is NamedTuple<T1, T2, T3, T4>)) {
return false;
}
@jjcat You're right! I had this fixed in a project but hadn't updated this gist yet. Fixed.
@KevinJones @aVolpe License added. Unlicense/public domain chosen over MIT or others because having to add copyright notices for a utility like this seems stupid.
Also added functions for unpacking via out param, since the delegate unpacker ended up being sort of annoying.