1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 73
Текст из файла (страница 73)
Однако у этогоограничения нет версий наподобие n e w (U) или n e w (U, V) для конструкторовс параметрами.Обобщенный интерфейс также может иметь ограничения:interfaceISettable<T>:whereТ:new()...Поиск обобщенного решенияВопрос в том, как поступить в случае, когда конструктору надо передавать параметры. Фабрика объектов, конструктор которых требует одного параметра, должна использовать в методе C r e a t e () код наподобие приведенного:public Т C r e a t e ( U и )/ / и — а р г у м е н т , который мы хотим// передать конструктору| {Т t = new Т ( ) ; / / Н о new Т ( ) не может.../ / И что т е п е р ь ? . .
.Глава15.Обобщенное программированиеприниматьаргументы369В результате следующий подход оказывается неработоспособным:Т t = n e w T ( u ) ; // Не р а б о т а е т/ / илиТ t = n e w T ( U ) ; // Не р а б о т а е тИтак, как же поступить, чтобы передать аргумент и новому объекту?О б х о д конструктора по умолчаниюТолько что описанная проблема решается путем использования обобщенного интерфейса. Чтобы позволить фабрике передавать параметры новым объектам, необходимоналожить определенные ограничения на производимые объекты: они должны реализовывать интерфейс наподобие I S e t t a b l e < T > :interfaceISettable<U>{void SetParameter(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 > :// Инстанцирование интерфейса дляclass Student:ISettable<string>типаstring{privates t r i n g name;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){SetParameter(name);////////Требуется наличиек о н с т р у к т о р а по умолчаниюК о н с т р у к т о р с однимпараметром}public void{this.nameSetParameter(stringname)//IIРеализацияISettable<string>= name;}//Прочиеметодыичлены-данныеклассаStudentПри этом вы можете создавать класс S t u d e n t и так, как это делали ранее, спомощью оператора new:students[0]370=newStudent("JuanValdez");Часть 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 gGenericFactoryl<Student,string> factl =new G e n e r i c F a c t o r y l < S t u d e n t ,string>();Вам также необходим метод C r e a t e () этой фабрики для получения объектов с установленным именем:// И с п о л ь з о в а н и е с т р о к о в о г о а р г у м е н т аs t u d e n t s [1]= factl.
Create ( "Richard Corey");Вот что происходит внутри метода 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 ereturn 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 c a n n o t .
. .> 'subclassName.methodName' hides inherited member 'baseclassName.methodName'. Usethe 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 . Наконец, С# не просто выводит имя к л а с с а — он предпочитает выводить его полностью, с указанием пространства имен, что тоже никак неприводит к сокращению сообщения.Это сообщение об ошибке может свидетельствовать о том, что вы забыли объявитьпеременную, как в следующем примере:for(index =0;index<10;index++){//...Какие-то действия...}Переменная i n d e x нигде не определена (см.
главу 3, "Объявление переменныхзначений", о том, как правильно объявлять переменные). Приведенный исходный тешдолжен быть переписан следующим образом:for(intindex=0;index<10;index++){II...Какие-то действия...}To же применимо и к членам класса (см. главу 6, "Объединение данных — классы имассивы").Не менее вероятно неверное написание имени переменной. Приведенный далеефрагмент исходного текста — хорошая иллюстрация такой ошибки,classStudent{publicpublics t r i n g sStudentName;i n t nID;}classMyClass{staticpublicvoidMyFunction(Students){Console.WriteLine("Имя =Console.WriteLine("Id=""+ s.sStudentName);+ s.nld);}}Здесь проблема заключается в том, что 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 . m e m b e r N a m e 'd o e s n o t c o n t a i n a defini1t i o n forv a r i a b l e N a m e ' . Более подробно о б этом вопросе рассказываетсяв главе 3, "Объявление переменных-значений".)Менее распространена, но все же попадает в десятку самых распространенных,ошибка, связанная с объявлением переменной в другой области видимости,c l a s s MyClass{staticpublicvoidAverageInput(){376i n t nSum = 0 ;i n t nCount = 0;while(true){// Считываем числоstring s = Console.ReadLine();int n = Int32.Parse(s);/ / В ы х о д , е с л и в в е д е н о о т р и ц а т е л ь Частьн о е ч VI.и с л оВеликолепныедесяткиif(n<0){break;}// НакоплениеnSum + = n ;nCount++;вводимыхчисел}/ / Вывод р е з у л ь т а т аConsole.WriteLine("Сумма равна" + nSum);C o n s o l e .
W r i t e L i n e ( " С р е д н е е р а в н о " + nSum / < n C o u n t ) ;// З д е с ь г е н е р и р у е т с я сообщение об ошибкеConsole.WriteLine("Завершающее значение — " + 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 * n A g e ;Проблема заключается в том, что 2 . 0 — переменная типа d o u b l e .