The apparent contradictions that appear in the question are caused because in one case the Equals function is called on a string object, and in the other case the == operator is called on the System.Object type. string and object implement equality differently from each other (value vs. reference respectively).
Beyond this fact, any type can define == and Equals differently, so in general they are not interchangeable.
Here’s an example using double (from Joseph Albahari’s note to §7.9.2 of the C# language specification):
double x = double.NaN;
Console.WriteLine (x == x); // False
Console.WriteLine (x != x); // True
Console.WriteLine (x.Equals(x)); // True
He goes on to say that the double.Equals(double) method was designed to work correctly with lists and dictionaries. The == operator, on the other hand, was designed to follow the IEEE 754 standard for floating point types.
In the specific case of determining string equality, the industry preference is to use neither == nor string.Equals(string) most of the time. These methods determine whether two string are the same character-for-character, which is rarely the correct behavior. It is better to use string.Equals(string, StringComparison), which allows you to specify a particular type of comparison. By using the correct comparison, you can avoid a lot of potential (very hard to diagnose) bugs.
Here’s one example:
string one = "Caf\u00e9"; // U+00E9 LATIN SMALL LETTER E WITH ACUTE
string two = "Cafe\u0301"; // U+0301 COMBINING ACUTE ACCENT
Console.WriteLine(one == two); // False
Console.WriteLine(one.Equals(two)); // False
Console.WriteLine(one.Equals(two, StringComparison.InvariantCulture)); // True
Both strings in this example look the same ("Café"), so this could be very tough to debug if using a naïve (ordinal) equality.