I'm working on a Complex number struct.
To display the numbers I've overridden the ToString method like this:

public override string ToString()
        {
            if (_imag >= 0)
                return String.Format("({0}+{1}i)", _real, _imag);
            else //imaginary part negative
                return String.Format("({0}{1}i)", _real, _imag);
        }

Works fine. My numbers get displayed as (a+bi) or (a-bi).
But I like also to implement the j format : a+bj or the ordered pair format : (a, b)
I know that I have to derive from IFormattable to do something like that. Looked here on daniweb and googled but did not find much info on how to do it.
I think I must implement public string ToString(string format, IFormatProvider formatProvider) and use a switch case to choose the format string. But it is all a bit hazy...
My questions :
Can I freely choose the format strings, even non standard ones?
Must I use IFormatProvider?

That depends. If the type is aware on how it should format itself then you have one issue versus if you want to let users decide how to format the object. Take for example a decimal being formatted with F2 or F0 -- that just controls the number of decimals. So if your class is aware that it is imaginary then you can control the behavior that way. You also need to implement a number of .ToString() calls.

You need IConvertible for Convert.ToString(yourStruct); You already overrode the object.ToString();
IFormattable for string.Format("{0}", yourStruct); You can freely choose the strings you want for IFormattable as they are passed to your interface handler. ie: string.Format("{0:i}", obj); for "i" string.Format("{0:j}", obj); for "j"
Also you could call obj.ToString("i");
I hope that answers your question if not then please explain it a little more

Your explantion sure shines a light in the dark.
I used this as my first information.
I overrode Object.ToString but this only works for one format or should I use a property to tell it: when you want to print yourself do it in j-format so I could check this and act accordinly in ToString, and forget about IFormattable or is that a bad idea? And I a still don't know what IFormatProvider is doing here. The site says forget about it and then starts to use it!

You should use both I think. If i'm not mistaken 'j' and 'i' both represent imaginary numbers and based on your code sample if (_imag >= 0) your type is aware that it is an imaginary number. That being said you have both of the scenarios I outlined above
1. Your class is aware internally of how to format based on real or imaginary.
2. You have two ways to format the value based on the user's preference of i or j (assuming i/j are the same?)

Or are the numbers always imaginary? I notice in the code snippet you posted you a+b and ab, was that intended to be "a-b" ?

I forgot to mention about IFormatProvider. The site is saying basically copy and paste their implementation of the IFormatProvider and ignore it. This is because I could specify my own IFormatProvider and pass it to your struct's Format() methods and you would return it based on my formatting and ignoring all of your code. This just makes it extensible but I don't know many people who write their own Format interfaces in addition to the authors

i and j represent both the same thing : the square root of -1.
I never store this in my struct, it just knows it and only displays it.
_real and _imag are private fields of Complex. These are my constructors as extra info:

#region Complex contructors.

        /// <summary>
        /// Initialize a complex number with a real and imaginary part
        /// </summary>
        /// <param name="real"></param>
        /// <param name="imag"></param>
        public Complex(double real, double imag)
        {
            this._real = real; this._imag = imag;
        }

        /// <summary>
        /// Create a new complex number out of an existing one
        /// </summary>
        /// <param name="z"></param>
        public Complex(Complex z)
        {
            this._real = z.realpart; this._imag = z.imaginarypart;
        } 
        #endregion

I my posts a is the real part and b is the imaginary part of the number. I use "({0}{1}i)" as a formatstring if _imag is negative and not "({0}-{1}i)" , because otherwise I would get a--bi as output.
So I am still wondering how my ToString gets to know if it should print a i or a j?
Your extra post about IFormatProvider makes that part of the issue cristal clear to me thanks.

I started on this code before I read this post so parts of it are wrong -- but it demonstrates what you want to know:

Here is my BigNumber

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace daniweb
{
  public class BigNumber : IConvertible, IFormattable
  {
    private int _real;

    public int Real
    {
      get { return _real; }
      set { _real = value; }
    }
    private int _imag;

    public int Imag
    {
      get { return _imag; }
      set { _imag = value; }
    }
    public BigNumber()
    {
      
    }

    public override string ToString()
    {
      return ((IFormattable)this).ToString(string.Empty, null);
    }

    #region IConvertible Members
    TypeCode IConvertible.GetTypeCode()
    {
      return TypeCode.Object;
    }
    bool IConvertible.ToBoolean(IFormatProvider provider)
    {
      throw new InvalidCastException();
    }
    byte IConvertible.ToByte(IFormatProvider provider)
    {
      throw new InvalidCastException();
    }
    char IConvertible.ToChar(IFormatProvider provider)
    {
      throw new InvalidCastException();
    }
    DateTime IConvertible.ToDateTime(IFormatProvider provider)
    {
      throw new InvalidCastException();
    }
    decimal IConvertible.ToDecimal(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    double IConvertible.ToDouble(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    short IConvertible.ToInt16(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    int IConvertible.ToInt32(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    long IConvertible.ToInt64(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    sbyte IConvertible.ToSByte(IFormatProvider provider)
    {
      throw new InvalidCastException();
    }
    float IConvertible.ToSingle(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    string IConvertible.ToString(IFormatProvider provider)
    {
      //Here is an important one
      return ((IFormattable)this).ToString(string.Empty, provider);
    }
    object IConvertible.ToType(Type conversionType, IFormatProvider provider)
    {
      return null; //you could implement this
    }
    ushort IConvertible.ToUInt16(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    uint IConvertible.ToUInt32(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    ulong IConvertible.ToUInt64(IFormatProvider provider)
    {
      return 0; //I guess you could implement this
    }
    #endregion

    #region IFormattable Members
    string IFormattable.ToString(string format, IFormatProvider formatProvider)
    {
      if (formatProvider != null) 
      { 
        ICustomFormatter fmt = formatProvider.GetFormat(this.GetType()) as ICustomFormatter; 
        if (fmt != null) 
        { 
          return fmt.Format(format, this, formatProvider); 
        } 
      }

      switch (format)
      {
        case "p": //ordered pair
          return string.Format("({0},{1})", _real, _imag);
        case "P":
          goto case "p";
        case "i":
          {
            string imaginaryFormat;
            if (string.Compare(format, "j", true) == 0)
              imaginaryFormat = "j";
            else
              imaginaryFormat = "i";
            //sample                0  -  3  i
            return string.Format("({0}{1}{2}{3})", 
              _real,  //0
              (_imag > 0 ? "+" : "-"),  //1
              _imag, //2
              imaginaryFormat
              );
          }
        case "I":
          goto case "i";
        case "j":
          goto case "i";
        case "J":
          goto case "i";
        default:
          goto case "i";
      }
      
    }
    #endregion

    //Maybe make life easier with overloads
    public string ToString(IFormatProvider provider)
    {
      return ((IFormattable)this).ToString(string.Empty, provider);
    }
    public string ToString(string Format)
    {
      return ((IFormattable)this).ToString(Format, null);
    }
  }
}

Calling it:

private void button2_Click(object sender, EventArgs e)
    {
      BigNumber big = new BigNumber();
      big.Real = 1;
      big.Imag = 2;
      string si = string.Format("{0:i}", big);
      string sI = string.Format("{0:I}", big);
      string sj = string.Format("{0:j}", big);
      string sJ = string.Format("{0:J}", big);
      string sp = string.Format("{0:p}", big);
      string sP = string.Format("{0:P}", big);
      string sXXX = string.Format("{0:XXX}", big);
      string sConv = Convert.ToString(big);
      string sToString = big.ToString();
      string sToStringI = big.ToString("I");
      string sToStringJ = big.ToString("J");
      string sToStringP = big.ToString("P");
      Console.WriteLine("si: " + si);
      Console.WriteLine("sI: " + sI);
      Console.WriteLine("sj: " + sj);
      Console.WriteLine("sJ: " + sJ);
      Console.WriteLine("sp: " + sp);
      Console.WriteLine("sP: " + sP);
      Console.WriteLine("sXXX: " + sXXX);
      Console.WriteLine("sConv: " + sConv);
      Console.WriteLine("sToString: " + sToString);
      Console.WriteLine("sToStringI: " + sToStringI);
      Console.WriteLine("sToStringJ: " + sToStringJ);
      Console.WriteLine("sToStringP: " + sToStringP);
    }

Results in:

si: (1+2i)
sI: (1+2i)
sj: (1+2j)
sJ: (1+2j)
sp: (1,2)
sP: (1,2)
sXXX: (1+2i)
sConv: (1+2i)
sToString: (1+2i)
sToStringI: (1+2i)
sToStringJ: (1+2j)
sToStringP: (1,2)
Comments
Scott, you earned thisone! (double if I could)

:-O :-O :-O
Scott!
I said it before, you are amazing!
Can give you any rep anymore today but I sure will do that tomorrow (It is now 3:25 PM at the place where I live.)
Thank you, thank you.
It is not that I like complex numbers so much, but like my calculator, it is a real live example one of which I always learn more than that dreaded "Custumer" or "Person" examples you find sprinkled all over the place.

I didn't want to edit my last post since you may have already reviewed the code -- but I guess per the CLR Law you should throw a FormatException for the default: in the switch. I would also throw an exception if ICustomFormatter fmt = formatProvider.GetFormat(this.GetType()) as ICustomFormatter; returns null because whoever implemented the interface is an idiot.

:-O :-O :-O
Scott!
I said it before, you are amazing!
Can give you any rep anymore today but I sure will do that tomorrow (It is now 3:25 PM at the place where I live.)
Thank you, thank you.
It is not that I like complex numbers so much, but like my calculator, it is a real live example one of which I always learn more than that dreaded "Custumer" or "Person" examples you find sprinkled all over the place.

You're always welcome :)

This question has already been answered. Start a new discussion instead.