Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C# для чайников

.pdf
Скачиваний:
183
Добавлен:
27.03.2015
Размер:
15.52 Mб
Скачать

 

В результате следующий подход оказывается неработоспособным:

Т

t

=

new

T ( u ) ;

//

Не

р а б о т а е т

 

 

 

 

 

/ /

или

 

Т

t

=

new

T ( U ) ;

//

Не

р а б о т а е т

Итак, как же поступить, чтобы передать аргумент и новому объекту?

О б х од конструктора по умолчанию

Только что описанная проблема решается путем использования обобщенного интер фейса. Чтобы позволить фабрике передавать параметры новым объектам, необходимо наложить определенные ограничения на производимые объекты: они должны реализовывать интерфейс наподобие I S e t t a b l e < T > :

i n t e r f a c e I S e t t a b l e < U >

{

v o i d S e t P a r a m e t e r ( U u ) ;

}

Тогда вы можете объявить фабрику для объектов с одним параметром следующим

образом:

 

 

 

 

 

// Т —

с о з д а в а е м ы й т и п ;

U —

тип

п а р а м е т р а к о н с т р у к т о р а

 

c l a s s G e n e r i c F a c t o r y l < T ,

U>

w h e r e

T: I S e t t a b l e < U > , new()

'{ }

Любой

тип T, производимый такой фабрикой, должен реализовывать

интерфейс

I S e t t a b l e < U > c U замененным реальным типом, таким как s t r i n g .

 

Использование найденного

решения

 

 

 

 

Если вы хотите производить объекты S t u d e n t посредством такой фабрики, а конст

руктор

S t u d e n t

требует один параметр,

скажем, s t r i n g , для передачи имени студен­

та, то класс S t u d e n t должен реализовать интерфейс

I S e t t a b l e < s t r i n g > :

/ / И н с т а н ц и р о в а н и е и н т е р ф е й с а д л я т и п а s t r i n g

 

c l a s s

S t u d e n t : I S e t t a b l e < s t r i n g >

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

p r i v a t e

s t r i n g n a m e ;

 

 

 

 

 

 

p u b l i c S t u d e n t ( ) { }

 

 

/ / Т р е б у е т с я н а л и ч и е

 

 

 

 

 

 

 

/ /

к о н с т р у к т о р а

п о умолчанию

p u b l i c

S t u d e n t ( s t r i n g name)

/ /

К о н с т р у к т о р с

одним

{

 

 

 

 

 

 

// п а р а м е т р о м

 

S e t P a r a m e t e r ( n a m e ) ;

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

p u b l i c

v o i d S e t P a r a m e t e r ( s t r i n g

name)

/ / Р е а л и з а ц и я

{

 

 

 

 

 

 

 

 

II I S e t t a b l e < s t r i n g >

t h i s . n a m e = n a m e ;

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

//

Прочие

методы

и

ч л е н ы - д а н н ы е

к л а с с а

S t u d e n t

 

При этом вы можете создавать класс S t u d e n t

и так, как это делали ранее, с

 

помощью оператора new:

 

 

 

 

 

 

 

s t u d e n t s [ 0 ]

=

new

S t u d e n t ( " J u a n

V a l d e z " ) ;

370

Часть V. За базовыми классами

Однако для использования фабрики вам нужен объект G e n e r i c F a c t o r y l < T , U>: // И н с т а н ц и р у е м с Т = S t u d e n t , U = s t r i n g

G e n e r i c F a c t o r y l < S t u d e n t ,

s t r i n g > f a c t l =

 

new

G e n e r i c F a c t o r y l < S t u d e n t , s t r i n g > ( ) ;

Вам также необходим метод C r e a t e ()

этой фабрики для получения объектов с ус­

тановленным именем:

 

// И с п о л ь з о в а н и е с т р о к о в о г о а р г у м е н т а

students

[1] =

f a c t l . C r e a t e ( " R i c h a r d C o r e y " ) ;

Вот что происходит внутри метода G e n e r i c F a c t o r y l < T , U> . C r e a t e ( ) :

public T

C r e a t e (U u)

 

{

T t = new T(); // Как и ранее, параметры конструктору / / н е п е р е д а ю т с я

t . S e t P a r a m e t e r ( u ) ; / / И с п о л ь з у е т с я м е т о д , п р е д о с т а в л е н н ы й / / при р е а л и з а ц и и I S e t t a b l e

return t;

}

Поскольку C r e a t e () может создавать только объекты без параметров, вы исполь­ зуете метод S e t P a r a m e t e r () для передачи параметра и объекту t. После этого можно вернуть объект S t u d e n t с установленным членом и м е н и — такой же, как если бы для его создания был вызван конструктор с одним параметром. Вы знаете, что тип Т имеет

метод S e t P a r a m e t e r () из-за ограничений, накладываемых

на класс S t u d e n t :

: I S e t t a b l e < s t r i n g > . Этот интерфейс гарантирует, что класс

S t u d e n t имеет метод

SetParameter ( ) .

 

Обсуждение решения

Насколько хорошее решение получено? Да, оно не является лучшим из тех, кото­ рые вы видели в своей практике. По сути, это обходной путь, но он приводит нас туда, куда нужно!

Но что делать, если конструктор объекта требует двух параметров? трех или че­ тырех? Увы, I S e t t a b l e < U > годится только для конструктора с одним параметром. В случае двух параметров вы должны добавить интерфейс I S e t t a b l e 2 < U , V>. Для трех — интерфейс I S e t t a b l e 3 < U , V, W> и т.д. Кроме того, для каждого из этих типов потребуется своя фабрика. Хорошая новость только в том, что крайне редко встречаются конструкторы более чем с пятью-шестью параметрами. Это и определяет, сколько ин­ терфейсов I S e t t a b l e < U , V , . . . > и фабрик вам понадобится.

Конечно, при желании класс может реализовывать как интерфейс I S e t t a b l e < U > , так и I S e t t a b l e 2 < U , V > . В этом случае вам потребуется реализовать как метод S e t P a r a m e t e r (U и ) , так и метод S e t P a r a m e t e r (U u,V v ) . ( Э т о — пере­ грузка, поскольку два метода S e t P a r a m e t e r () имеют разные сигнатуры.)

Глава 15. Обобщенное программирование

371

Часть VI

Великолепные десятки

Какая книга из серии Для чайников была бы полна без этой части? С# отлично умеет искать ошибки в ваших программах — вы, навер­ ное, и сами это заметили. Однако сообщения об ошибках часто на­ поминают военные шифровки — это тоже наверняка бросилось вам в глаза. В главе 16, "Десять наиболее распространенных ошибок компиляции", будут рассмотрены десять наиболее часто встречаю­ щихся ошибок в программах С#, а также будег рассказано, что они означают и как с ними бороться.

Многие читатели интересуются местом С# в семье объектноориентированных языков программирования и его связью с наиболее распространенным объектно-ориентированным языком — С++. В гла­ ве 17, "Десять основных отличий С# и С++", вкратце описаны отличия этих двух языков, включая различия между обобщенными классами С# и шаблонами С++.

Глава 16

Десять наиболее распространенных ошибок компиляции

>The name 'memberName' does not exist in the class or namespace 'className'

>Cannot implicitly convert type 'x' into 'y'

>'className.memberName' is inaccessible due to its protection level

> Use of unassigned local variable 'n'

} Unable to copy the file 'programName.exe' to 'programName.exe.' The process cannot ...

>'subclassName.methodName' hides inherited member 'baseclassName.methodName'. Use the new keyword if hiding was intended.

>'subclassName' : cannot inherit from sealed class 'baseclassName'

>'className' does not implement interface member 'methodName'

>'methodName' : not all code paths return a value

>j expected

# очень строго подходит к программам и буквально с лупой выискивает в них мельчайшие ошибки. В этой главе будут рассмотрены 10 наиболее распростра­ ненных сообщений об ошибках. Но перед тем как приступить к этому, следует сделать несколько замечаний. С# достаточно многословен, и при работе над книгой мне попада­ лись ошибки, сообщения о которых не помещались на одной странице, так что я обрезал некоторые сообщения до одной-двух первых строк. Кроме того, в сообщениях об ошибке С# часто вставляет имена переменных, методов или классов, с которыми эти ошибки связаны. Вместо конкретных имен здесь я использую такие имена, как v a r i a b l e N a m e , memberName или c l a s s N a m e . Наконец, С# не просто выводит имя класса— он пред­ почитает выводить его полностью, с указанием пространства имен, что тоже никак не

приводит к сокращению сообщения.

Это сообщение об ошибке может свидетельствовать о том, что вы забыли объявить переменную, как в следующем примере:

f o r ( i n d e x = 0; i n d e x < 1 0 ; i n d e x + + )

{

// . . . К а к и е - т о д е й с т в и я . . .

}

Переменная i n d e x нигде не определена (см. главу 3, "Объявление переменныхзначений", о том, как правильно объявлять переменные). Приведенный исходный теш должен быть переписан следующим образом:

f o r ( i n t i n d e x = 0 ; i n d e x < 1 0 ; i n d e x + + )

{

II . . . К а к и е - т о д е й с т в и я . . .

}

To же применимо и к членам класса (см. главу 6, "Объединение данных — классы и массивы").

Не менее вероятно неверное написание имени переменной. Приведенный далее фрагмент исходного текста — хорошая иллюстрация такой ошибки,

c l a s s S t u d e n t

{

p u b l i c

s t r i n g s S t u d e n t N a m e ;

p u b l i c

i n t n I D ;

}

c l a s s M y C l a s s

{

s t a t i c p u b l i c v o i d M y F u n c t i o n ( S t u d e n t s )

{

C o n s o l e . W r i t e L i n e ( " И м я C o n s o l e . W r i t e L i n e ( " I d

=" + s . s S t u d e n t N a m e ) ;

=" + s . n l d ) ;

}

}

Здесь проблема заключается в том, что M y F u n c t i o n O обращается к члену nld, в то время как настоящее имя члена — n I D . Хотя это очень похожие имена, С# не считает их одинаковыми. Программист написал n l d , но никакого n l d не существу­ ет, и С# честно пытается об этом сообщить. (В данном случае сообщение об ошибке

немного

отличается:

' c l a s s . memberName ' d o e s

n o t

c o n t a i n a defini ­

t i o n f o r

1 v a r i a b l e N a m e '. Более подробно об

этом

вопросе рассказывается

в главе 3, "Объявление переменных-значений".)

 

 

 

 

Менее распространена, но все же попадает в десятку самых распространенных,

ошибка, связанная с объявлением переменной в другой области видимости,

c l a s s MyClas s

 

 

 

 

 

{

 

 

 

 

 

 

 

 

s t a t i c

p u b l i c

v o i d A v e r a g e I n p u t ( )

 

 

 

 

{

 

 

 

 

 

 

 

 

 

i n t

nSum =

0;

 

 

 

 

 

i n t n C o u n t = 0;

 

 

 

 

 

w h i l e ( t r u e )

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

/ /

Считываем ч и с л о

 

 

 

 

 

s t r i n g s = C o n s o l e . R e a d L i n e ( ) ;

 

 

 

 

376

i n t n = I n t 3 2 . P a r s e ( s ) ;

 

 

 

 

/ /

Выход,

Часть

VI.

Великолепные десятки

 

е с л и в в е д е н о о т р и ц а т е л ь н о е

ч и с л о

 

i f

(n <

0)

 

 

 

{

 

 

 

 

 

 

b r e a k ;

 

 

 

 

}

 

 

 

 

 

/ /

Н а к о п л е н и е вводимых

ч и с е л

 

 

nSum +=

n;

 

 

 

n C o u n t + + ;

 

 

 

}

 

 

 

 

 

// Вывод

р е з у л ь т а т а

 

 

 

C o n s o l e . W r i t e L i n e ( " С у м м а р а в н а " + n S u m ) ;

C o n s o l e . W r i t e L i n e ( " С р е д н е е

р а в н о

"

+ nSum / < n C o u n t ) ;

// З д е с ь

г е н е р и р у е т с я сообщение

об

ошибке

C o n s o l e . W r i t e L i n e ( " З а в е р ш а ю щ е е з н а ч е н и е — " + s ) ;

Последняя строка этой функции некорректна. Дело в том, что переменная s ограни­ чена областью видимости, в которой определена, и вне цикла w h i l e она не видна (см. главу 5, "Управление потоком выполнения").

Эта ошибка обычно указывает на попытку использования двух переменных различно­ го типа в одном выражении, например:

int nAge = 1 0 ;

 

 

 

// Генерация сообщения

об ошибке

int n F a c t o r e d A g e =

2 . 0

*

nAge;

Проблема заключается

в том,

что 2 . 0 — переменная типа d o u b l e . Целое значение

нАде умножается на число с плавающей точкой 2.0, что в результате дает значение типа double. С# не в состоянии автоматически сохранить значение типа d o u b l e в перемен­ яй nFactoredAge типа i n t , потому что при этом может оказаться потерянной ин­ формация — скорее всего, дробная часть числа с плавающей точкой.

Некоторые преобразования не настолько очевидны, как в следующем примере:

class MyClass

{

s t a t i c p u b l i c f l o a t F l o a t T i m e s 2 ( f l o a t f )

{

// Г е н е р и р у е т с я сообщение об ошибке f l o a t f R e s u l t = 2 . 0 * f;

r e t u r n f R e s u l t ;

Вы можете решить, что здесь все в порядке, так как везде используется тип f l o a t . Яо дело в том, что 2.0 имеет тип не f l o a t , a d o u b l e , d o u b l e , умноженный на f l o a t , нет double. С# не может сохранить значение типа d o u b l e в переменной типа f l o a t в-за возможной потери информации, в данном случае — количества знаков результата, но приводит к снижению точности (см. главу 3, "Объявление переменных-значений").

Неявное преобразование легко запутывает неискушенного читателя. В приведенном да- фрагменте исходного текста функция F l o a t T i m e s 2 () работает вполне корректно:

та

16. Десять

наиболее распространенных ошибок компиляции

377

c l a s s MyClas s

{

s t a t i c p u b l i c f l o a t

F l o a t T i m e s 2 ( f l o a t f )

{

 

 

 

/ / Все

о т л и ч н о

р а б о т а е т

f l o a t

f R e s u l t

= 2

* f ;

r e t u r n

f R e s u l t ;

 

}

 

 

 

}

Константа 2 имеет тип i n t . i n t , умноженный на f l o a t , дает f l o a t , который вполне может быть сохранен в переменной f R e s u l t типа f l o a t .

Такое же сообщение об ошибке может возникать и при выполнении операций не "неестественными" типами. Например, нельзя сложить две переменные типа char, не С# может при необходимости конвертировать переменную c h a r в значение int.

приводит к следующему: c l a s s M y C l a s s

{

s t a t i c p u b l i c v o i d S o m e F u n c t i o n ( )

{

c h a r c l = ' a ' ; c h a r c 2 = ' b ' ;

// Я не з н а ю , ч т о э т о должно о з н а ч а т ь , но в с е р а в н о это // н е в е р н о — х о т я и не по т о й п р и ч и н е , о к о т о р о й вы / / д у м а е т е

c h a r сЗ = cl + с 2 ;

} }

Сложение двух символов не имеет смысла, но С# все равно попытается это сделать Поскольку сложение для типа c h a r не определено, он преобразует cl и с2 в значение типа i n t и выполнит их сложение (технически c h a r представляет собой интегральный тип). К сожалению, результат этого сложения преобразовать обратно в c h a r не удастся без помощи со стороны программиста (см. главу 3, "Объявление переменных значений").

Большинство (хотя и не все) преобразований без проблем выполняется при их явном указании. Так, следующая функция компилируется без каких-либо нареканий:

c l a s s MyClas s

{

s t a t i c p u b l i c f l o a t F l o a t T i m e s 2 ( f l o a t f )

{

/ / З д е с ь и с п о л ь з о в а н о я в н о е f l o a t f R e s u l t = ( f l o a t ) ( 2 . 0 r e t u r n f R e s u l t ;

пр е о б р а з о в а н и е

*f ) ;

}

}

Результат умножения 2 . 0*f имеет тип d o u b l e , как и ранее, однако программист' явно указал, что он хочет преобразовать полученное значение к типу f l o a t , даже если это приведет к потере информации (см. главу 3, "Объявление переменных-значений").

Второй подход к проблеме может состоять в том, чтобы явно указать необходимы! тип константы:

378

Часть VI.

Великолепные десятки

Г л а в

 

 

class M y C l a s s

{

s t a t i c p u b l i c f l o a t F l o a t T i m e s 2 ( f l o a t f )

{

/ / З д е с ь 2 . OF — к о н с т а н т а т и п а f l o a t f l o a t f R e s u l t = 2 . OF * f;

r e t u r n f R e s u l t ;

}

В этой версии функции использована константа 2.0 типа f l o a t , а не d o u b l e , как принято по умолчанию, f l o a t , умноженный на f l o a t , дает f l o a t .

Данная ошибка указывает на попытку функции обратиться к члену, на обращение к которому она не имеет прав. Например, метод в одном классе может пытаться обра­ титься к закрытому члену другого класса (см. главу 11, "Классы"), как показано в приве­ денном фрагменте исходного текста,

public c l a s s M y C l a s s

{

p u b l i c v o i d S o m e F u n c t i o n ( )

{

Y o u r C l a s s

uc = new Y o u r C l a s s ( ) ;

// M y C l a s s не и м е е т д о с т у п а к з а к р ы т о м у ч л е н у

uc.nPrivateMember = 1;

}

 

}

 

public c l a s s

Y o u r C l a s s

"{

private int nPrivateMember = 0;

}

Обычно такая ошибка не столь очевидна. Зачастую оказывается просто забытым де­ скриптор члена или всего класса, а по умолчанию член класса является закрытым. Так, nPrivateMember остается закрытым в следующем фрагменте исходного текста:

class

M y C l a s s

//

Доступ к

к л а с с у

по умолчанию - i n t e r n a l

(

 

 

 

 

 

 

p u b l i c v o i d S o m e F u n c t i o n ( )

 

{

 

 

 

 

 

 

Y o u r C l a s s

uc

=

new Y o u r C l a s s ( ) ;

// M y C l a s s не и м е е т д о с т у п а к з а к р ы т о м у ч л е н у

uc . n P r i v a t e M e m b e r = 1;

 

 

}

 

 

 

 

 

 

'public

c l a s s

Y o u r C l a s s

 

 

(

 

 

 

 

 

 

int

n P r i v a t e M e m b e r

= 0 ;

/ / Этот

ч л е н - з а к р ы т ы й !

Глава 16. Десять наиболее распространенных ошибок компиляции

379

Кроме того, несмотря на то что функция S o m e F u n c t i o n () объявлена как public к ней нельзя обратиться из классов других модулей, поскольку класс MyClass сам себе является внутренним.

Мораль этой истории — всегда указывайте уровень защиты ваших классов и их чле нов. Кроме того, не объявляйте открытые члены во внутренних классах — это том сбивает с толку.

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

p u b l i c c l a s s M y C l a s s

 

 

 

{

 

 

 

 

 

p u b l i c v o i d S o m e F u n c t i o n ( )

 

 

{

 

 

 

 

 

i n t

n ;

 

 

 

 

//

Все в

п о р я д к е ,

т а к к а к

С# т о л ь к о в о з в р а щ а е т

з н а ч е н и е

//

в п;

в функцию

з н а ч е н и е

э т о й п е р е м е н н о й не

п е р е д а е т с я

S o m e O t h e r F u n c t i o n ( o u t n ) ;

}

p u b l i c v o i d S o m e O t h e r F u n c t i o n ( o u t i n t n )

{

n = 1;

}

В данном случае переменной п в функции S o m e F u n c t i o n () не присваивается ни

какого значения, поскольку это выполняется

в функции SomeOtherFunctiond

Функция S o m e O t h e r F u n c t i o n () игнорирует

значение out-аргумента, как если 6ы|

его не существовало вовсе. (В главе 3, "Объявление переменных-значений", рассказыва ется о переменных, а ключевое слово o u t рассмотрено в главе 7, "Функции функций".)

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

1.Успешно собрали программу.

2.Когда вы запустили программу с помощью команды Debug ^Start without

Debugging, то получили сообщение Нажмите < E n t e r > д л я завершения п р о г р а м м ы . . ., но по какой-то причине не сделали этого (таким образом, вы-

380

Часть VI. Великолепные д е с я т и

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]