logo
C#, 320 стр

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

Арифметический тип, как показано в таблице 3.1, распадается на 11 подтипов. На рис. 4.1 показана схема преобразований внутри арифметического типа.

Рис. 4.1.  Иерархия преобразований внутри арифметического типа

Диаграмма, приведенная на рисунке, позволяет ответить на ряд важных вопросов, связанных с существованием преобразований между типами. Если на диаграмме задан путь (стрелками) от типа А к типу В, то это означает существование неявного преобразования из типа А в тип В. Все остальные преобразования между подтипами арифметического типа существуют, но являются явными. Заметьте, что циклов на диаграмме нет, все стрелки односторонние, так что преобразование, обратное к неявному, всегда должно быть задано явным образом.

Путь, указанный на диаграмме, может быть достаточно длинным, но это вовсе не означает, что выполняется вся последовательность преобразований на данном пути. Наличие пути говорит лишь о существовании неявного преобразования, а само преобразование выполняется только один раз, - из типа источника А в тип назначения В.

Иногда возникает ситуация, при которой для одного типа источника может одновременно существовать несколько типов назначений и необходимо осуществить выбор цели - типа назначения. Такие проблемы выбора возникают, например, при работе с перегруженными методами в классах.

Понятие перегрузки методов и операций подробно будет рассмотрено в последующих лекциях (см. лекцию 8).

Диаграмма, приведенная на рис. 4.1, и в этом случае помогает понять, как делается выбор. Пусть существует две или более реализации перегруженного метода, отличающиеся типом формального аргумента. Тогда при вызове этого метода с аргументом типа T может возникнуть проблема, какую реализацию выбрать, поскольку для нескольких реализаций может быть допустимым преобразование аргумента типа T в тип, заданный формальным аргументом данной реализации метода. Правило выбора реализации при вызове метода таково: выбирается та реализация, для которой путь преобразований, заданный на диаграмме, короче. Если есть точное соответствие параметров по типу (путь длины 0), то, естественно, именно эта реализация и будет выбрана.

Давайте рассмотрим еще один тестовый пример. В класс Testing включена группа перегруженных методов OLoad с одним и двумя аргументами. Вот эти методы:

/// <summary>

/// Группа перегруженных методов OLoad

/// с одним или двумя аргументами арифметического типа.

/// Если фактический аргумент один, то будет вызван один из

/// методов, наиболее близко подходящий по типу аргумента.

/// При вызове метода с двумя аргументами возможен

/// конфликт выбора подходящего метода, приводящий

/// к ошибке периода компиляции.

/// </summary>

void OLoad(float par)

{

Console.WriteLine("float value {0}", par);

}

/// <summary>

/// Перегруженный метод OLoad с одним параметром типа long

/// </summary>

/// <param name="par"></param>

void OLoad(long par)

{

Console.WriteLine("long value {0}", par);

}

/// <summary>

/// Перегруженный метод OLoad с одним параметром типа ulong

/// </summary>

/// <param name="par"></param>

void OLoad(ulong par)

{

Console.WriteLine("ulong value {0}", par);

}

/// <summary>

/// Перегруженный метод OLoad с одним параметром типа double

/// </summary>

/// <param name="par"></param>

void OLoad(double par)

{

Console.WriteLine("double value {0}", par);

}

/// <summary>

/// Перегруженный метод OLoad с двумя параметрами типа long и long

/// </summary>

/// <param name="par1"></param>

/// <param name="par2"></param>

void OLoad(long par1, long par2)

{

Console.WriteLine("long par1 {0}, long par2 {1}", par1, par2);

}

/// <summary>

/// Перегруженный метод OLoad с двумя параметрами типа

/// double и double

/// </summary>

/// <param name="par1"></param>

/// <param name="par2"></param>

void OLoad(double par1, double par2)

{

Console.WriteLine("double par1 {0}, double par2 {1}",par1, par2);

}

/// <summary>

/// Перегруженный метод OLoad с двумя параметрами типа

/// int и float

/// </summary>

/// <param name="par1"></param>

/// <param name="par2"></param>

void OLoad(int par1, float par2)

{

Console.WriteLine("int par1 {0}, float par2 {1}",par1, par2);

}

Все эти методы устроены достаточно просто. Они сообщают информацию о типе и значении переданных аргументов. Вот тестирующая процедура, вызывающая метод OLoad с разным числом и типами аргументов:

/// <summary>

/// Вызов перегруженного метода OLoad. В зависимости от

/// типа и числа аргументов вызывается один из методов группы.

/// </summary>

public void OLoadTest()

{

OLoad(x); OLoad(ux);

OLoad(y); OLoad(dy);

// OLoad(x,ux);

// conflict: (int, float) и (long,long)

OLoad(x,(float)ux);

OLoad(y,dy); OLoad(x,dy);

}

Заметьте, один из вызовов закомментирован, так как он приводит к конфликту на этапе трансляции. Для устранения конфликта при вызове метода пришлось задать явное преобразование аргумента, что показано в строке, следующей за строкой-комментарием.

Рис. 4.2.  Вывод на печать результатов теста OLoadTest

Прежде чем посмотреть на результаты работы тестирующей процедуры, попробуйте понять, какой из перегруженных методов вызывается для каждого из вызовов. В случае каких-либо сомнений используйте схему, приведенную на 4.1.

Приведу все-таки некоторые комментарии. При первом вызове метода тип источника - int, а тип аргумента у четырех возможных реализаций соответственно float, long, ulong, double. Явного соответствия нет, поэтому нужно искать самый короткий путь на схеме. Так как не существует неявного преобразования из типа int в тип ulong (на диаграмме нет пути), то остаются возможными три реализации. Но путь из int в long короче, чем остальные пути, поэтому будет выбрана long-реализация метода.

Следующий вызов демонстрирует еще одну возможную ситуацию. Для типа источника uint существуют две возможные реализации, и пути преобразований для них имеют одинаковую длину. В этом случае выбирается та реализация, для которой на диаграмме путь показан сплошной, а не пунктирной стрелкой, потому будет выбрана реализация с параметром long.

Рассмотрим еще ситуацию, приводящую к конфликту. Первый аргумент в соответствии с правилами требует вызова одной реализации, а второй аргумент будет настаивать на вызове другой реализации. Возникнет коллизия, не разрешимая правилами C# и приводящая к ошибке периода компиляции. Коллизию требуется устранить, например, как это сделано в примере. Обратите внимание - обе реализации допустимы, и существуй даже только одна из них, ошибки бы не возникало.