Зачем выполнять проверку formatType при реализации интерфейса ICustomFormatter в C#? - PullRequest
0 голосов
/ 14 января 2020

Я читаю C # 6.0 в двух словах, в книге говорится, что пользователь также может написать свой собственный поставщик форматов, который работает в сочетании с существующими типами. А на странице 245 приведен пример кода, как показано ниже:

Я не знаю, можно ли пропустить проверку при реализации функции GetFormat? Могу ли я просто использовать

return this 

вместо

if (formatType == typeof(ICustomFormatter)) 
    return this;
return null; 

в этой реализации? Как я думаю, formatType всегда будет typeof ICustomFormatter в соответствии с объявлением класса, потому что он реализовал интерфейс ICustomFormatter?

using System;
using System.Globalization;
using System.Text;

namespace Page245
{
    class Program
    {
        static void Main(string[] args)
        {
            double n = -123.45;
            IFormatProvider fp = new WordyFormatProvider();
            Console.WriteLine(string.Format(fp, "{0:C} in words is {0:W}", n));
        }
        public class WordyFormatProvider : IFormatProvider, ICustomFormatter
        {
            static readonly string[] _numberWords =
            "zero one two three four five six seven eight nine minus point".Split();
            IFormatProvider _parent;   // Allows consumers to chain format providers
            public WordyFormatProvider() : this(CultureInfo.CurrentCulture) { }
            public WordyFormatProvider(IFormatProvider parent)
            {
                _parent = parent;
            }
            public object GetFormat(Type formatType)
            {
                if (formatType == typeof(ICustomFormatter)) // Can this check be omitted?, just return this directly, as formatType will always be typeof ICustomFormatter as per class declaration, it implements the ICustomFormatter interface??
                    return this;
                return null;
            }
            public string Format(string format, object arg, IFormatProvider prov)
            {
                // If it's not our format string, defer to the parent provider:
                if (arg == null || format != "W")
                    return string.Format(_parent, "{0:" + format + "}", arg);
                StringBuilder result = new StringBuilder();
                string digitList = string.Format(CultureInfo.InvariantCulture, "{0}", arg);
                foreach (char digit in digitList)
                {
                    int i = "0123456789-.".IndexOf(digit);
                    if (i == -1) continue;
                    if (result.Length > 0) result.Append(' ');
                    result.Append(_numberWords[i]);
                }
                return result.ToString();
            }
        }

    }
}

Я изменил код, и результаты не изменились, но я хотел бы спросить есть ли потенциальные недостатки, если я изменю код, как упомянуто? Спасибо за любые комментарии или ответ.

Он записывает в консоль, как показано ниже, и результат не изменяется после изменения - 123,45 евро словами минус один два три целых четыре пять

1 Ответ

2 голосов
/ 14 января 2020

Бывают случаи, когда IFormatProvider.GetFormat вызывается с formatType, установленным на что-то отличное от типа вашего форматера.

Давайте используем действительно простой тест IFormatProvider:

public class CustomFormatter : IFormatProvider
{
    public object GetFormat(Type formatType)
    {
        Console.WriteLine("Called with " + formatType);
        return null;
    }
}

Теперь давайте попробуем кое-что:

string s = 3.ToString(new CustomFormatter());

Здесь GetFormat вызывается с typeof(NumberFormatInfo).

string s = DateTime.Now.ToString(new CustomFormatter());

Здесь, GetFormat вызывается с typeof(DateTimeFormatInfo).

string s = string.Format(new CustomFormatter(), "{0}", 3);

Здесь GetFormat сначала вызывается с typeof(ICustomFormatter), а затем с typeof(NumberFormatInfo).


Теперь верно, что в текущая реализация. NET, если вы передадите свой собственный IFormatProvider в string.Format, он сначала запросит у него ICustomFormatter, а если он вернет null, для NumberFormatInfo / DateTimeFormatInfo (при необходимости).

Однако , вы не можете на это полагаться. Кто-то может использовать ваш IFormatProvider где-то еще (например, передать объекту, который реализует IFormattable), и в этом случае ваш IFormatProvider может быть запрошен для другого formatType. Может случиться так, что реализация string.Format изменится в будущем, и он запрашивает NumberFormatInfo до или , а также и ICustomFormatter.

The docs для IFormatProvider.GetFormat скажем:

Возвращает

Экземпляр объекта, указанный formatType, если IFormatProvider реализация может предоставить объект такого типа; в противном случае, null.

Чтобы все работало, сейчас и в будущем, вы должны следовать этому. Если ваш IFormatProvider может предоставить экземпляр запрошенного типа, он должен это сделать. В противном случае он должен вернуть null.


Что касается того, почему string.Format сначала вызывает наш CustomFormatter с typeof(ICustomFormatter), а затем с typeof(NumberFormatInfo), см. this do c:

способ форматирования аргументов

Элементы форматирования обрабатываются последовательно с начала строки. Каждый элемент формата имеет индекс, соответствующий объекту в списке аргументов метода. Метод Format извлекает аргумент и получает его строковое представление следующим образом:

  • Если аргумент равен null, метод вставляет String.Empty в строку результата. Вам не нужно беспокоиться об обработке NullReferenceException для null аргументов.

  • Если вы вызываете перегрузку Format(IFormatProvider, String, Object[]) и реализация IFormatProvider.GetFormat объекта провайдера возвращает ненулевая реализация ICustomFormatter, аргумент передается методу ICustomFormatter.Format(String, Object, IFormatProvider). Если элемент формата содержит аргумент formatString, он передается в качестве первого аргумента методу. Если реализация ICustomFormatter доступна и создает ненулевую строку, эта строка возвращается как строковое представление аргумента; в противном случае выполняется следующий шаг.

  • Если аргумент реализует интерфейс IFormattable, вызывается его реализация IFormattable.ToString.

  • Вызывается метод ToString без параметров, который либо переопределяет, либо наследует от реализации базового класса.

Аргумент не null, поэтому мы пропускаем первый пункт .

Мы вызвали перегрузку string.Format, которая принимает IFormatProvider, но наша реализация IFormatProvider.GetFormat вернула null, поэтому мы пропустили второй пункт.

Наш аргумент, Int32, выполняет реализацию IFormattable, поэтому вызывается его реализация IFormattable.ToString (и передается наша IFormatProvider). Int32.ToString(IFormatProvider provider) звонит IFormatProvider.GetFormat и переходит в typeof(NumberFormatInfo).

...