Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Otvety_k_zachetu.docx
Скачиваний:
43
Добавлен:
10.05.2015
Размер:
233.91 Кб
Скачать

Int Square(int I)

{

// Store input argument in a local variable.

int input = i;

return input * input;

}

По умолчанию при передаче методу типа значения передается копия объекта, а не сам объект. Поэтому изменения в аргументе не оказывают влияния на исходную копию в вызывающем методе. Тип значения по ссылке можно передать с помощью ключевого слова ref. Дополнительные сведения см. в разделе Передача параметров типа значения (руководство по программированию в C#). Полный список встроенных типов значений см. в разделе Таблица типов значений (Справочник по C#).

Ссылочные типы передаются по ссылке. При передаче методу объекта, имеющего ссылочный тип, ссылка указывает на исходный объект, а не на копию. Изменения объекта, осуществляемые с помощью этой ссылки, будут доступны и в вызывающем методе. В следующем примере ключевое слово class указывает на то, что создается объект ссылочного типа.

public class SampleRefType

{

public int value;

}

Теперь при передаче методу объекта этого типа объект будет передаваться по ссылке. Пример.

public static void TestRefType()

{

SampleRefType rt = new SampleRefType();

rt.value = 44;

ModifyObject(rt);

Console.WriteLine(rt.value);

}

static void ModifyObject(SampleRefType obj)

{

obj.value = 33;

}

В этом примере выполняются те же действия, что и в предыдущем примере. Но поскольку используется ссылочный тип, изменения в методе ModifyObject относятся к объекту, созданному в методе created in the TestRefType. Поэтому в методе TestRefType на экран будет выведено значение 33.

  1. Возврат методом значений. Тип void.

Методы могут возвращать значения вызывающим их объектам. Если тип возвращаемого значения, указываемый перед именем метода, не равен void, для возвращения значения используется ключевое слово return. В результате выполнения инструкции с ключевым словом return, после которого указано значение нужного типа, вызвавшему метод объекту будет возвращено это значение. Кроме того, ключевое слово return останавливает выполнение метода. Если тип возвращаемого значения void, инструкцию return без значения все равно можно использовать для завершения выполнения метода. Если ключевое слово return отсутствует, выполнение метода завершится, когда будет достигнут конец его блока кода. Для возврата значений методами с типом возвращаемого значения отличным от void необходимо обязательно использовать ключевое слово return. Например, в следующих двух методах ключевое слово return служит для возврата целочисленных значений.

class SimpleMath

{

public int AddTwoNumbers(int number1, int number2)

{

return number1 + number2;

}

public int SquareANumber(int number)

{

return number * number;

}

}

Чтобы использовать возвращаемое методом значение в вызываемом методе, вызов метода можно поместить в любое место кода, где требуется значение соответствующего типа. Кроме того, возвращаемое значение можно присвоить переменной. Например, следующие два примера кода выполняют одну и ту же задачу.

int result = obj.AddTwoNumbers(1, 2);

obj.SquareANumber(result);

obj.SquareANumber(obj.AddTwoNumbers(1, 2));

Использовать локальную переменную для хранения значения (в данном случае это переменная result) необязательно. Эта переменная может упростить читаемость кода либо быть необходимой, если необходимо сохранить исходное значение аргумента для целой области метода.

  1. Рекурсивные методы

Рекурсивным называют метод, если он вызывает сам себя в качестве вспомогательного. В основе рекурсивного метода лежит так называемое "рекурсивное определение" какого-либо понятия. Классическим примером рекурсивного метода является метод, вычисляющий факториал. 

Из курса математики известно, что 0!=1!=1, n!=1*2*3…*n. С другой стороны n!=(n-1)!*n. Таким образом, известны два частных случая параметра n, а именно n=0 и n=1, при которых мы без каких-либо дополнительных вычислений можем определить значение факториала. Во всех остальных случаях, то есть для n>1, значение факториала может быть вычислено через значение факториала для параметра n-1. Таким образом, рекурсивный метод будет иметь вид:

{

   static long F(int n)  //рекурсивный метод

   {

      if (n==0 || n==1) 

       return 1;    //нерекурсивная ветвь

       else return n*F(n-1);  //шаг рекурсии - повторный вызов метода с другим параметром

    }

    static void Main()

    {

      Console.Write("n=");

       int n =int.Parse( Console.ReadLine());

       long f=F(n); //нерекурсивный вызов метода F

       Console.WriteLine("{0}!={1}",n, f); 

      }

}

Метод с прямой рекурсией обычно содержит следующую структуру:

if (<условие>)  

<оператор>; 

else <вызов данного метода с другими параметрами>; 

В качестве <условия> обычно записываются некоторые граничные случаи параметров, передаваемых рекурсивному методу, при которых результат его работы заранее известен, поэтому далее следует простой оператор или блок, а в ветви else происходит рекурсивный вызов данного метода с другими параметрами. 

 

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

 

Следует понимать, что любой рекурсивный метод можно преобразовать в обычный метод. И практически любой метод можно преобразовать в рекурсивный, если выявить рекуррентное соотношение между вычисляемыми в методе значениями.

  1. Перегрузка методов.

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

Перегрузка методов позволяет программистам на С# многократно использовать одни и те же имена методов, меняя лишь передаваемые аргументы. Это очень полезно по крайней мере в двух сценариях. Первый:  вам нужно иметь единое имя метода, поведение которого немного различается в зависимости от типа переданных аргументов. Допустим, у вас есть класс, отвечающий за протоколирование, позволяющий вашему приложению записывать на диск диагностическую информацию. Чтобы немного повысить гибкость класса, вы можете создать несколько форм метода Write,определяющих тип записываемой информации. Кроме собственно строки, подлежащей записи, метод также может принимать строку идентификатора ресурса. Без перегрузки методов вам пришлось бы реализовать отдельные методы наподобие WriteString и WriteFrom-Resourceld для каждой ситуации. Однако перегрузка методов позволяет реализовать оба эти метода под именемWriteEntry, они будут различаться лишь типом параметра:

using System; class Log { public Log(string fileName) { // Открыть файл fileName и перейти в его конец. } public void WriteEntry(string entry) { Console.WriteLine(entry); } public void WriteEntry(int resourceld) { Console.WriteLine ("Получение строки на основе идентификатора ресурса и запись в журнал"); } } class OverloadlngtApp { public static void Main() { Log log = new LogC'Mou файл"); log.WriteEntry("3anncb № 1"); log.WriteEntry(42); } }

Второй сценарий, в котором выгодно применять перегрузку метода, — использование конструкторов, которые в сущности представляют собой методы, вызываемые при создании экземпляра объекта. Допустим, вы хотите создать класс, который может быть построен несколькими способами. Например, он использует описатель (int) или имя (string) файла, чтобы открыть его. Поскольку правила С# диктуют, что у конструктора класса должно быть такое же имя, как и у самого класса, вы не можете просто создать разные методы для переменных каждого типа. Вместо этого нужно использовать перегрузку конструктора: using System; 

class File { > class CommaDeliinitedFile { public CommaDelimitedFile(String fileName) { Console.WriteLine("Конструктор, использующий имя файла"); } public CommaDelimitedFile(File file) { Console.WriteLineC"Конструктор, использующий файловый объект"); } } class Overloading2App { public static void HainQ { File file = new File(); ConmaDelimitedFile flle2 = new ConnaDelinitedFile(file);  ConnaDelinitedFile flleS = new ConmaDelinitedFile CMiM некоторого файла"); > }

О перегрузке метода важно помнить следующее: список аргументов каждого метода должен отличаться. Поэтому следующий код не будет компилироваться, так как единственное различие между двумя версиями OverloadingSApp.Foo — тип возвращаемого значения: 

using System; class OverloadingSApp < void Foo(double input) { Console.WriteLine("Overloading3App.Foo(double)"); > // ОШИБКА: методы отличаются лишь типом возвращаемого значения. // Код компилироваться не будет, double Foo(double input) { Console.WriteLine("Overloading3App.Foo(double) (вторая версия)"); } public static void Main() { ' 4 ^.._ OverloadingSApp app = new Overloading3App(); double i = 5; app.Foo(l); > }

  1. Конструкторы. Назначение, определение, вызов.

Конструктор – специальный метод класса, который автоматически вызывается при создании объектов данных классов. Конструктор инициализирует объект при его создании. У конструктора такое же имя, как и у его класса, а с точки зрения синтаксиса он подобен методу. Но у конструкторов нет возвращаемого типа, указываемого явно. Ниже приведена общая форма конструктора:

доступ имя_класса(список_параметров)

{

// тело конструктора

}

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

Каждый класс С# снабжается конструктором по умолчанию, который при необходимости может быть переопределен. По определению такой конструктор никогда не принимает аргументов. После размещения нового объекта в памяти конструктор по умолчанию гарантирует установку всех полей в соответствующие стандартные значения. Если вы не удовлетворены такими присваиваниями по умолчанию, можете переопределить конструктор по умолчанию в соответствии со своими нуждами.

Конструктор также может принимать один или несколько параметров. В конструктор параметры вводятся таким же образом, как и в метод. Для этого достаточно объявить их в скобках после имени конструктора.

Для простых типов (по значению) С# поддерживает два способа инициализации:

  • если переменная является локальной для метода или out-параметром метода (не инициализирована начальным значением, ссылка), то компилятор будет настаивать, чтобы в коде явно устанавливалось значение переменной до того, как она будет использована;

  • во всех остальных ситуациях (включая статические поля-члены и поля экземпляров) компилятор инициализирует переменные после их создания значениями по умолчанию.

Если указать начальное значение переменной (поля) при ее объявлении (int x = 10), то это значение будет всегда использоваться для инициализации.

Для класса можно написать статический конструктор без параметров который будет исполнен всего одни раз. Выполняется он при создании объектов одного класса (может понадобиться для инициализации статических переменных). Статический конструктор не имеет можификатора доступа, он никогда не вызывается C#, а только самой .NET при загрузке класса и может осуществлять доступ только к статическим полям класса.

public class Authenticator

{

static Authenticator()

{

// задаём минимальную длиняу пароля

minPasswordLength = 6;

}

private static readonly byte minPasswordLength;

}

Константыне величины (const) являются неявно статическими. Величина является постоянной по определению, поэтому нет необходимости хранить ее копии для каждого экземпляра класса. На них всегда можно ссылаться через имя класса:

int x = Authenticator.MaxLength;

Синтаксически не корректно объявлять величины const статическими (static)! Ключевое слово readonly допускает, что поле будет константным, но для определения его начального значения необходимо выполнить некоторые вычисления. Присвоить значение полю readonly можно только внутри конструктора(!). Поле readonly может быть полем экземпляра, а не статическим полем и иметь различные значения для каждого экземпляра класса. Это означает, что для того, чтобы поле было статическим его необходимо явно объявить таким. Если не инициализировать поле readonly в конструкторе, то оно будет иметь значению по умолчанию для данного типа или то значение, которое было присвоено ему при объявлении.

Вызов конструктора из других конструкторов

public class Authenticator

{

// сначала вызыв. другой конструктор

public Authenticator() : this("none")

{

// что то делаем

}

public Authenticator(string initialPassword)

{

password = initialPassword;

}

private string password;

}

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

Ключевые слова base и this являются единственными допустимыми ключевыми словами, которые можно указывать в строке вызова другого конструктора. Все остальное сгенерирует ошибку компиляции. Замечу также, что может быть указан только один конструктор.

Конструкторы производных классов

public GenericCustomer(string initName)

{

name = initName;

}

public class PayAsYouGoCustomer : GenericCustomer

{

// конструктор без параметров в базовом классе

// отсутствует поэтому необходимо вызивать собственный

public PayAsYouGoCustomer(string initName) : base(initName)

{

// ..

}

...

Nevermore60Customer Alex = new Nevermore60Customer("Alex");

Теперь конструктор будет создаваться только в том случае, если в конструктор будет передаваться строка, содержащая имя пользователя.

  1. Деструкторы. Назначение, определение, вызов.

В языке С# имеется возможность определить метод, который будет вызываться непосредственно перед окончательным уничтожением объекта системой "сборки мусора". Такой метод называется деструктором и может использоваться в ряде особых случаев, чтобы гарантировать четкое окончание срока действия объекта. Например, деструктор может быть использован для гарантированного освобождения системного ресурса, задействованного освобождаемым объектом. Ниже приведена общая форма деструктора:

~имя_класса () {

// код деструктора

}

Finalize() наиболее близко соответствует концепции традиционного деструктора. Если объявить в классе метод Finalize() он будет автоматически вызываться при уничтожении экземпляра класса. Finalize() является детерминированным, т.е. нет способа выяснить, когда будет вызван сборщик мусора и соответственно Finalize(). Следовательно, в метод Finalize() нельзя помещать код, который должен выполняться в какое-то определенное время. Запустить сборщик мусора можно с помощью метода System.GC.Collect(). Не рекомендуется реализовывать метод Finalize() если только класс действительно не нуждается в нем. Это наносит сильный удар по производительности при сборке мусора.

Практически во всех случаях освобождение ресурсов средой исполнения .NET является наилучшим решением. В основном Finalize() надо использовать тогда, когда объект работает с ресурсами, которыми .NET не управляет, - например, файлы и соединения с базами данных. Реализация:

~MyClass ()

{

//..

}

Деструкторы Dispose() и Close()

Они работают таким образом, что при желании вы можете определить один из них для класса, понимая при этом, что клиентский код обязан явно вызывать этот метод для освобождения ресурсов. Ресурсы освобождаются сразу после того, как отпадает надобность в них. Недостаток: вы полагаетесь на то, что клиентский код будет вести себя корректно и вызовет этот метод (однако это может и не произойти).

Типичное решение – использование Dispose() и Close() совместно с Finalize():

public class myClass : MasterClass

{

private int x;

public void Dispose()

{

// освобождаем ресурсы

//...

GC.SuppressFinalize(this);

// для объекта (this) не

// нужно больше вызивать Finalize

}

protected override void Finalize()

{

// освобождаем ресурсы

base.Finalize();

}

}

В приведенном примере: Если клиентский код не забудет вызвать Dispose(), ресурсы будут освобождены вовремя. Если же забудет, то Finalize() будет вызван при сборке мусора. Метод GC.SuppressFinalize() информирует среду исполнения о том, что для объекта переданного в качестве параметра не нужно больше вызывать Finalize().

Разница между Close() и Dispose() состоит главным образом в соглашении о их применению. Close() предполагает, что ресурс может быть позже открыт, в то время как вызов Dispose() означает, что клиент закончил работу с этим ресурсом навсегда. Можно реализовать оба метода.

C# предлагает синтаксис, который можно использовать для гарантии того, что Dispose (но не Close() (!) будет вызван для объекта автоматически, и его не нужно вызывать вручную, когда ссылка на него выйдет из области видимости (using):

Выражение using, за которым в скобках следует определение переменной по ссылке, поместит эту переменную в область видимости, задаваемую фигурными скобками. Когда переменная выйдет из этой области видимости, будет автоматически вызван ее метод Dispose().

Лучшая альтернатива определить класс унаследованным от интерфейса IDisposable:

public class RessourceG : IDisposable

{

public void Dispose()

{

Console.WriteLine("Resource Dispose!");

}

}

Наследование от IDisposable вынуждает производный класс реализовать метод Dispose(), если этого не будет сделано, возникает ошибка при компиляции. Выигрыш – компилятор может убедиться, что объект определенный в выражении using имеет метод Dispose(), который он может вызвать автоматически.

  1. Статические компоненты классов.

Статический класс в основном такой же, что и нестатический класс, но имеется одно отличие: нельзя создавать экземпляры статического класса. Другими словами, нельзя использовать ключевое слово new для создания переменной типа класса. Поскольку нет переменной экземпляра, доступ к членам статического класса осуществляется с использованием самого имени класса. Например, если имеется статический класс, называемый UtilityClass, имеющий открытый метод, называемый MethodA, вызов метода выполняется, как показано в следующем примере.

UtilityClass.MethodA();

Статический класс может использоваться как обычный контейнер для наборов методов, работающих на входных параметрах, и не должен возвращать или устанавливать каких-либо внутренних полей экземпляра. Например, в библиотеке классов платформы .NET Framework статический класс System.Math содержит методы, выполняющие математические операции, без требования сохранять или извлекать данные, уникальные для конкретного экземпляра класса Math. Это значит, что члены класса применяются путем задания имени класса и имени метода, как показано в следующем примере.

double dub = -3.14;

Console.WriteLine(Math.Abs(dub));

Console.WriteLine(Math.Floor(dub));

Console.WriteLine(Math.Round(Math.Abs(dub)));

// Output:

// 3.14

// -4

// 3

Как и в случае с типами всех классов сведения о типе для статического класса загружаются средой CLR .NET Framework, когда загружается программа, которая ссылается на класс. Программа не может точно указать, когда загружается класс. Но гарантируется загрузка этого класса, инициализация его полей и вызов статического конструктора перед первым обращением к классу в программе. Статический конструктор вызывается только один раз, и статический класс остается в памяти на время существования домена приложения, в котором находится программа.

Следующий список предоставляет основные характеристики статического класса:

Содержит только статические члены.

Создавать его экземпляры нельзя.

Он закрыт.

Не может содержать конструкторов экземпляров.

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

Статические классы запечатаны, поэтому их нельзя наследовать. Они не могут быть унаследованы ни от каких классов, кроме Object. Статические классы не могут содержать конструктор экземпляров, но могут содержать статический конструктор. Нестатические классы также должен определять статический конструктор, если класс содержит статические члены, для которых нужна нетривиальная инициализация. Дополнительные сведения см. в разделе Статические конструкторы (Руководство по программированию в C#).

Ниже приведен пример статического класса, содержащего два метода, преобразующих температуру по Цельсию в температуру по Фаренгейту и наоборот.

public static class TemperatureConverter

{

public static double CelsiusToFahrenheit(string temperatureCelsius)

{

// Convert argument to double for calculations.

double celsius = Double.Parse(temperatureCelsius);

// Convert Celsius to Fahrenheit.

double fahrenheit = (celsius * 9 / 5) + 32;

return fahrenheit;

}

public static double FahrenheitToCelsius(string temperatureFahrenheit)

{

// Convert argument to double for calculations.

double fahrenheit = Double.Parse(temperatureFahrenheit);

// Convert Fahrenheit to Celsius.

double celsius = (fahrenheit - 32) * 5 / 9;

return celsius;

}

}

class TestTemperatureConverter

{

static void Main()

{

Console.WriteLine("Please select the convertor direction");

Console.WriteLine("1. From Celsius to Fahrenheit.");

Console.WriteLine("2. From Fahrenheit to Celsius.");

Console.Write(":");

string selection = Console.ReadLine();

double F, C = 0;

switch (selection)

{

case "1":

Console.Write("Please enter the Celsius temperature: ");

F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine());

Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);

break;

case "2":

Console.Write("Please enter the Fahrenheit temperature: ");

C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine());

Console.WriteLine("Temperature in Celsius: {0:F2}", C);

break;

default:

Console.WriteLine("Please select a convertor.");

break;

}

// Keep the console window open in debug mode.

Console.WriteLine("Press any key to exit.");

Console.ReadKey();

}

}

/* Example Output:

Please select the convertor direction

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