public static void TestTuples()
        {
            // Notice that they don’t all have to be the same data type.
            (int, string, bool)values = (10, "Moamen", true);

            // you can use Implicity typed var
            var varValues = (Math.PI, "Math", false);

            // By default, the compiler assigns each property the name ItemX,
            //  where X represents the one based position in the tuple.
            //  X starts from 1 to n the number of elements in tuple.
            Console.WriteLine(values.Item1);
            Console.WriteLine(values.Item2);
            Console.WriteLine(values.Item3);

            Console.WriteLine(values.GetType().Name);

            Console.WriteLine(values.Item1.GetType().FullName);
            Console.WriteLine(values.Item2.GetType().FullName);
            Console.WriteLine(values.Item3.GetType().FullName);

            #region Named and unnamed tuples - Deconstruction Tuples

            // Named and unnamed tuples
            //------------------------------------------------------------------------------------------
            // The ValueTuple struct - drived from ValueType - has fields named Item1, Item2, Item3, and so on,
            //      similar to the properties defined in the existing Tuple types.
            //      These names are the only names you can use for unnamed tuples.
            //      When you do not provide any alternative field names to a tuple,
            //      you've created an unnamed tuple.

            // Named tuples still have elements named Item1, Item2, Item3 and so on.
            //      But they also have synonyms for any of those elements that you have named.

            //  In Named tuples, The compiled Microsoft Intermediate Language (MSIL) does not
            //      include the names you've given to tuple elements.

            // Unnamed tuples
            (int, bool)tuple1 = (10, true);
            var tuple2 = (10, true);
            (int, bool)tuple3 = (integer : 10, boolean : true); // right side names ignored by compiler

            // named tuples
            (int integer, bool boolean)tuple4 = (10, true);
            var tuple5 = (integer : 10, boolean : true);

            (int integer, bool boolean)tuple6 = (myInt : 10, myBool : true); // right side names ignored by compiler

            // Deconstruction tuple
            (int integer, bool boolean) = (10, true);

            var(integer1, boolean1) = (10, true);

            (var integer2, var boolean2) = (10, true);


            // Specific names can be added to each property in the tuple on either the right side or the left side of
            //      the statement.
            // Left Side Named Properties
            //------------------------------------------------------------------------------------------
            (int MyInt, string Mystr, bool MyBool)rightSideTuple = (10, "Moamen", true);
            // the properties on the tuple can be accessed using the field names as well as the ItemX notation

            Logger.Title("rightSideTuple with ItemX call");
            Console.WriteLine(rightSideTuple.Item1);
            Console.WriteLine(rightSideTuple.Item2);
            Console.WriteLine(rightSideTuple.Item3);

            Logger.Title("rightSideTuple with Named Properties call");
            Console.WriteLine(rightSideTuple.MyInt);
            Console.WriteLine(rightSideTuple.MyBool);
            Console.WriteLine(rightSideTuple.Mystr);

            // var with Right Side Named Properties - Names as part of the tuple initialization -:
            //------------------------------------------------------------------------------------------
            var leftSideTupleWithVar = (MyInt : 10, MyStr : "Moamen", MyBool : true);

            Logger.Title("leftSideTuple with ItemX call");
            Console.WriteLine(leftSideTupleWithVar.Item1);
            Console.WriteLine(leftSideTupleWithVar.Item2);
            Console.WriteLine(leftSideTupleWithVar.Item3);

            Logger.Title("leftSideTupleWithVar with Named Properties call");
            Console.WriteLine(leftSideTupleWithVar.MyInt);
            Console.WriteLine(leftSideTupleWithVar.MyStr);
            Console.WriteLine(leftSideTupleWithVar.MyBool);


            // var with Left Side Named Properties
            //------------------------------------------------------------------------------------------
            var(MyInt, MyStr, MyBool) = (10, "Moamen", true);
            Logger.Title("leftSideTupleWithVar with ItemX call");
            Console.WriteLine(leftSideTupleWithVar.Item1);
            Console.WriteLine(leftSideTupleWithVar.Item2);
            Console.WriteLine(leftSideTupleWithVar.Item3);

            Logger.Title("leftSideTupleWithVar with Named Properties call");
            Console.WriteLine(leftSideTupleWithVar.MyInt);
            Console.WriteLine(leftSideTupleWithVar.MyStr);
            Console.WriteLine(leftSideTupleWithVar.MyBool);


            // Both Sides Named Properties (Left names ignored and can't be used)
            //------------------------------------------------------------------------------------------
            // While it is not a compiler error to assign names on both sides of the statement,
            //      if you do, the right side will be ignored, and only the left-side names are used.
            (int MyInt, string Mystr, bool MyBool)bothSidesTuple = (MyIntValue : 10, MyStringValue : "Moamen", MyBoolValue : true);

            Logger.Title("bothSidesTuple with ItemX call");
            Console.WriteLine(bothSidesTuple.Item1);
            Console.WriteLine(bothSidesTuple.Item2);
            Console.WriteLine(bothSidesTuple.Item3);

            Logger.Title("bothSidesTuple with Left Named Properties call");
            Console.WriteLine(bothSidesTuple.MyInt);
            Console.WriteLine(bothSidesTuple.Mystr);
            Console.WriteLine(bothSidesTuple.MyBool);

            // ERROR: Cannot Access Tuples With Left Named Properties call when there are Right Names
            //Logger.Title("bothSidesTuple with Right Named Properties call");
            //Console.WriteLine(bothSidesTuple.MyIntValue);
            //Console.WriteLine(bothSidesTuple.MyStrValue);
            //Console.WriteLine(bothSidesTuple.MyBoolValue);


            // Left Side Named Properties with Right Side Explicit properties Types -without var keyword-
            //------------------------------------------------------------------------------------------
            // NOTE: Note that when setting the names on the right, you must use the keyword var.
            //      Setting the data types specifically(even without custom names) triggers
            //      the compiler to use the left side, assign the properties using the ItemX notation,
            //      and ignore any of the custom names set on the right.The following two examples ignore
            //      the MyIntValue, MyStringValue and MyBoolValue names:

            (int, string, bool)leftSideTuple = (MyIntValue : 10, MyStringValue : "Moamen", MyBoolValue : true);


            // So the only way to access the last tuple is to use ItemX notation
            Logger.Title("leftSideTuple with Right Side Explicit properties Types -without var keyword-");
            Console.WriteLine(leftSideTuple.Item1);
            Console.WriteLine(leftSideTuple.Item2);
            Console.WriteLine(leftSideTuple.Item3);

            #endregion

            #region Tuple Projection Initializers
            // Tuple Projection Initializers
            //------------------------------------------------------------------------------------------
            // Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to
            //  initialize the tuple. This is referred to as tuple projection initializers.

            Logger.Title("Tuple Projection Initializers");
            double sum   = 12.5;
            int    count = 5;
            // projected names
            var accumulation = (count, sum);
            Console.WriteLine("accumulation.count = " + accumulation.count);
            Console.WriteLine("accumulation.sum = " + accumulation.sum);

            // You can use Projected Names, or update it with new Explicit Names.
            // If an explicit name is given, that takes precedence over any projected name.
            // and compile error with appear if you call elements with projected names

            // explicit names
            var accumulation2 = (count2 : count, sum2 : sum);
            Console.WriteLine("accumulation.count2 = " + accumulation2.count2);
            Console.WriteLine("accumulation.sum2 = " + accumulation2.sum2);
            // compile Error if you use projected names, as explicit names canceled projected names
            //Console.WriteLine("accumulation.sum = " + accumulation2.sum);
            //Console.WriteLine("accumulation.count = " + accumulation2.count);

            // For any field where an explicit name is not provided, an applicable implicit name is projected.

            var stringContent = "The answer to everything";
            var mixedTuple    = (42, stringContent);
            Console.WriteLine($"mixedTuple = ({mixedTuple.Item1} , {mixedTuple.stringContent})");

            // Mixed tuple with explicit name for first element, and projected name for second element.
            var mixedTuple2 = (integer : 42, stringContent);
            Console.WriteLine($"mixedTuple2 = ({mixedTuple2.integer} , {mixedTuple2.stringContent})");

            // There are two conditions where candidate field names are not projected onto the tuple field:
            // 1- When the candidate name is a reserved tuple name.
            //      Examples include Item3, ToString or Rest.
            // 2- When the candidate name is a duplicate of another tuple field name,
            //      either explicit or implicit.

            // Neither of these conditions cause compile-time errors.Instead,
            //      the elements without projected names do not have semantic
            //      names projected for them.

            // Projection Failure First Case:
            int num    = 10;
            int Item2  = 20;
            var tuple7 = (Item2, num);
            // projection failed as Item2 ia << Reserved Tuple Name >> , and now:
            // first element can be accessed only with implicit name (Item1)
            Console.WriteLine(tuple7.Item1);
            // second element can be accessed with projected name (num) and implicit name (Item2)
            Console.WriteLine(tuple7.num);
            Console.WriteLine(tuple7.Item2);

            // Projection Failure Second Case:

            var point1 = (X : 10, Y : 20);
            var point2 = (X : 30, Y : 40);
            // Projection failed because of Names Ambiguity due to duplication of another tuple field name
            var xCoords = (point1.X, point2.X);
            // we can access xCoords only with Implicit names ItemX Notation
            Console.WriteLine(xCoords.Item1);
            Console.WriteLine(xCoords.Item2);

            //Console.WriteLine(xCoords.X);

            // These situations do not cause compiler errors because that would be a
            //      breaking change for code written with C# 7.0, when tuple field name
            //      projections were not available.

            #endregion

            #region Equality in Tuples

            // Equality in Tuples

            // Beginning with C# 7.3, tuple types support the == and != operators.
            // These operators work by comparing each member of the left argument to
            // each member of the right argument in order. These comparisons short-circuit.
            Logger.Title("Equality in Tuples");
            var left  = (a : 5, b : 10);
            var right = (a : 5, b : 10);
            Console.WriteLine(left == right); // displays 'true'

            // Tuple equality also performs implicit conversions on each member of both tuples.
            // These include lifted conversions, widening conversions, or other implicit conversions.

            // Tuple equality performs lifted conversions if one of the tuples is a nullable tuple
            Logger.Title("lifted conversions if one of the tuples is a nullable tuple");
            var left2 = (a : 10, b : 20);
            (int?a, int?b)nullableRight = (10, 20);
            Console.WriteLine(left2 == nullableRight);

            // converted type of left is (long, long)
            (long a, long b)longTuple = (5, 10);
            Console.WriteLine(left == longTuple); // Also true

            // comparisons performed on (long, long) tuples
            (long a, int b)longFirst  = (5, 10);
            (int a, long b)longSecond = (5, 10);
            Console.WriteLine(longFirst == longSecond); // Also true

            // The names of the tuple members do not participate in tests for equality.However,
            //  if one of the operands is a tuple literal with explicit names, the compiler
            //  generates warning CS8383 if those names do not match the names of the other operand.

            (int a, string b)pair    = (1, "Hello");
            (int z, string y)another = (1, "Hello");
            Console.WriteLine(pair == another);            // true. Member names don't participate.
            Console.WriteLine(pair == (z: 1, y: "Hello")); // warning: literal contains different member names

            //Finally, tuples may contain nested tuples.
            // Tuple equality compares the "shape" of each operand through nested tuples.

            (int, (int, int))nestedTuple = (1, (2, 3));
            Console.WriteLine(nestedTuple == (1, (2, 3)));

            // It's a compile time error to compare two tuples for equality (or inequality)
            //      when they have different shapes. The compiler won't attempt any deconstruction
            //      of nested tuples in order to compare them.

            #endregion


            #region Assignment and tuples
            //The language supports assignment between tuple types that have the same number of elements,
            //      where each right-hand side element can be implicitly converted to its corresponding
            //      left hand side element. Other conversions aren't considered for assignments.
            //      It's a compile time error to assign one tuple to another when they have different shapes.
            //      The compiler won't attempt any deconstruction of nested tuples in order to assign them.
            //      Let's look at the kinds of assignments that are allowed between tuple types.

            // The 'arity' and 'shape' of all these tuples are compatible.
            // The only difference is the field names being used.
            var unnamed        = (42, "The meaning of life");
            var anonymous      = (16, "a perfect square");
            var named          = (Answer : 42, Message : "The meaning of life");
            var differentNamed = (SecretConstant : 42, Label : "The meaning of life");

            //  all of these assignments work:
            unnamed = named;

            named = unnamed;
            // 'named' still has fields that can be referred to
            // as 'answer', and 'message':
            Console.WriteLine($"{named.Answer}, {named.Message}");

            // unnamed to unnamed:
            anonymous = unnamed;

            // named tuples.
            named = differentNamed;
            // The field names are not assigned. 'named' still has
            // fields that can be referred to as 'answer' and 'message':
            Console.WriteLine($"{named.Answer}, {named.Message}");

            // With implicit conversions:
            // int can be implicitly converted to long
            (long, string)conversion = named;

            // explicit conversion of tuple
            (short, string)conversion2 = ((short)named.Answer, named.Message);

            // Notice that the names of the tuples are not assigned. The values of the elements
            //      are assigned following the order of the elements in the tuple.

            // Tuples of different types or numbers of elements are not assignable:
            // Does not compile.
            // CS0029: Cannot assign Tuple(int,int,int) to Tuple(int, string)
            //var differentShape = (1, 2, 3);
            //named = differentShape;



            #endregion

            #region Tuples As Method Return Values

            // Tuples As Method Return Values
            //------------------------------------------------------------------------------------------
            //out parameters were used to return more than one value from a method call.There
            //are additional ways to do this, such as creating a class or structure specifically to return
            //the values.But if this class or struct is only to be used as a data transport for one method,
            //that is extra work and extra code that doesn’t need to be developed.
            //Tuples are perfectly suited for this task, are lightweight, and are easy to declare and use.

            var samples = FillTheseValues();
            Console.WriteLine($"Int is: {samples.a}");
            Console.WriteLine($"String is: {samples.b}");
            Console.WriteLine($"Boolean is: {samples.c}");


            var nameTuple = SplitName("Moamen");
            Console.WriteLine($"Name: {nameTuple.first}{(nameTuple.second == "" ? "" : " ")}{nameTuple.second}{(nameTuple.last == "" ? "" : " ")}{nameTuple.last}");

            nameTuple = SplitName("Moamen Mohammed");
            WriteName(nameTuple);

            nameTuple = SplitName("Moamen Mohammed Soroor");
            WriteName(nameTuple);

            nameTuple = SplitName("Moamen Mohammed Gamal Soroor");
            WriteName(nameTuple);

            nameTuple = SplitName("Moamen Mohammed Gamal Mohammed Soroor");
            WriteName(nameTuple);

            nameTuple = SplitName("Moamen MG.Soroor");
            WriteName(nameTuple);

            // Discards with Tuples
            //------------------------------------------------------------------------------------------
            // Following up on the SplitNames() example, suppose you know that you need only
            //      the first and last names and don’t care about the second.

            var(first, _, last) = SplitName("Moamen Mohammed Soroor");
            Console.WriteLine($"{first}:{last}");

            var(first2, _, _) = SplitName("Moamen Mohammed Soroor");
            Console.WriteLine($"{first2}");

            #endregion


            #region Deconstructing with Tuples

            // Deconstruction
            //------------------------------------------------------------------------------------------
            // You can unpackage all the items in a tuple by deconstructing the tuple returned by a method.
            //      There are three different approaches to deconstructing tuples.



            //MyPoint Sructure Variable
            MyPoint p1 = new MyPoint(10, 20);

            // deconstructing
            var pointValues = p1.Deconstruct();
            Console.WriteLine($"pointValues.px = {pointValues.XPos}");
            Console.WriteLine($"pointValues.py = {pointValues.YPos}");


            // deconstructing
            var(XPos2, YPos2) = p1.Deconstruct();
            Console.WriteLine($"px = {XPos2}");
            Console.WriteLine($"py = {YPos2}");

            #endregion
        }