Generic LinkedList with add/exists/delete/dlear/iteration functionality I've just been going through core data structures and algorithms recently, wanting to get back in touch with the core fundamentals. I'm starting simple with a LinkedList implementation in C#. The functionality works and was able to test it. I'm just wondering if yo see anything with the code that could be improved.

using System.Collections;
using System.Collections.Generic;

namespace DataStructuresAndAlgorithms.DataStructures.LinkedList
{
    public class SinglyLinkedList&lt;T&gt; : IEnumerable&lt;T&gt;
    {
        public SinglyLinkedListNode&lt;T&gt; Head { get; set; }
        public SinglyLinkedListNode&lt;T&gt; Tail { get; set; }

        public void AddToEnd(T value)
        {
            var newNode = new SinglyLinkedListNode&lt;T&gt;
            {
                Value = value,
                Next = null
            };
            if (Head == null &amp;&amp; Tail == null)
            {
                Head = newNode;
                Tail = newNode;
            }
            if (Head == Tail)
            {
                Head.Next = newNode;
                Tail = newNode;
            }
            else
            {
                var existingLast = Tail;
                existingLast.Next = newNode;
                Tail = newNode;
            }
        }

        public void AddToBeginning(T value)
        {
            var newNode = new SinglyLinkedListNode&lt;T&gt;
            {
                Value = value,
                Next = null
            };
            if (Head == null &amp;&amp; Tail == null)
            {
                Head = newNode;
                Tail = newNode;
            }
            else
            {
                var existingHead = Head;
                Head = newNode;
                Head.Next = existingHead;
            }
        }

        public bool Exists(T value)
        {
            var head = Head;
            while(head != null)
            {
                if (head.Value.Equals(value))
                {
                    return true;
                }
                head = head.Next;
            }
            return false;
        }

        public void Delete(T value)
        {
            var head = Head;
            SinglyLinkedListNode&lt;T&gt; previous = null;
            while(head != null)
            {
                if (head.Value.Equals(value))
                {
                    if (previous == null)
                    {
                        Head = Head.Next;
                    }
                    else
                    {
                        previous.Next = head.Next;
                    }
                }
                previous = head;
                head = head.Next;
            }
        }

        public void Clear()
        {
            Head = null;
            Tail = null;
        }

        public IEnumerator&lt;T&gt; GetEnumerator()
        {
            var head = Head;
            while(head != null)
            {
                yield return head.Value;
                head = head.Next;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    public class SinglyLinkedListNode&lt;T&gt;
    {
        public T Value { get; set; }
        public SinglyLinkedListNode&lt;T&gt; Next { get; set; }
    }
}
I would implement ICollection&lt;T&gt; in addition to IEnumerable&lt;T&gt;. It should be easy and straightforward.
Public setters of the Head and Tail properties will allow users of your class to corrupt the structure of linked list.
Not sure if you need to expose Head and Tail as properties at all, as they are the only ones who expose the SinglyLinkedListNode&lt;T&gt; instances used internally.
AddToEnd implementation can be simplified. The case of Head == Tail doesn't differ from else clause. So the method can be refactored as following (note that the non-null Head implies non-null Tail, this should be guaranteed by the design of your class):

public void AddToEnd(T value)
{
    var newNode = new SinglyLinkedListNode&lt;T&gt;
    {
        Value = value,
        Next = null
    };

    if (Head == null)
    {
        Head = newNode;
    }
    else
    {
        Tail.Next = newNode;
    }

    Tail = newNode;
}

Similar refactorings can be applied to AddToBeginning. When you create a new node to be inserted at the beginning, its Next property will always refer to original Head node, whether it was null (list is empty) or actual node. Thus the method can be reduced to:

public void AddToBeginning(T value)
{
    Head = new SinglyLinkedListNode&lt;T&gt;
    {
        Value = value,
        Next = Head
    };

    if (Tail == null)
    {
        Tail = Head;
    }
}

Delete method has a bug - it will never assign null to Tail, even if all elements are deleted from the list.