Если вы не инициализировали ссылочный тип и хотите установить или прочитать одно из его свойств, он будет генерировать исключение NullReferenceException.
Пример:
Person p = null;
p.Name = "Harry"; // NullReferenceException occurs here.
Вы можно просто избежать этого, проверив, является ли переменная не нулевой:
Person p = null;
if (p!=null)
{
p.Name = "Harry"; // Not going to run to this point
}
Чтобы полностью понять, почему выбрано исключение NullReferenceException, важно знать разницу между типами значений и ссылочные типы .
Итак, если вы имеете дело со типами значений, NullReferenceExceptions не может произойти. Хотя вам нужно поддерживать оповещение при работе со ссылочными типами!
Только ссылочные типы, как следует из названия, могут содержать ссылки или буквально буквально ничто (или «нуль»). Если типы значений всегда содержат значение.
Типы ссылок (эти должны быть проверены):
Типы значений (вы можете просто игнорировать эти):
Я использую их:
TGpStringListHelper = class helper for TStringList
public
function Last: string;
function Contains(const s: string): boolean;
function FetchObject(const s: string): TObject;
procedure Sort;
procedure Remove(const s: string);
end; { TGpStringListHelper }
Я видел, как они использовались для создания доступных методов класса, согласованных между классами: Добавление Open / Close и Show / Hide ко всем классам данного типа, а не только к активным и видимым свойствам.
Это очень похоже на методы расширения в C # 3 (и VB9). Наилучшее использование, которое я видел для них, это расширения до IEnumerable<T>
(и IQueryable<T>
), которые позволяют LINQ работать с произвольными последовательностями:
var query = someOriginalSequence.Where(person => person.Age > 18)
.OrderBy(person => person.Name)
.Select(person => person.Job);
(или что-то еще, конечно). Все это выполнимо, поскольку методы расширения позволяют эффективно объединять вызовы статическим методам, которые принимают тот же тип, что и они.
Они очень полезны для плагинов. Например, предположим, что ваш проект определяет определенную структуру данных и сохраняется на диске определенным образом. Но тогда какая-то другая программа делает что-то очень похожее, но файл данных отличается. Но вы не хотите раздувать ваш EXE с кучей кода импорта для функции, которую многие ваши пользователи не будут использовать. Вы можете использовать плагиновую структуру и поместить импортеров в плагин, который будет работать следующим образом:
type
TCompetitionToMyClass = class helper for TMyClass
public
constructor Convert(base: TCompetition);
end;
И затем определите конвертер. Одно предостережение: помощник класса не является классом friend . Этот метод будет работать только в том случае, если можно полностью настроить новый объект TMyClass через его общедоступные методы и свойства. Но если вы можете, он работает очень хорошо.
Я бы не рекомендовал их использовать, так как я прочитал этот комментарий:
«Самая большая проблема с помощниками класса, начиная с использования их в ваших собственных приложениях, заключается в том, что только один помощник класса для данного класса может быть доступен в любой момент ». ... »То есть, если у вас есть два помощника в области видимости, только компилятор распознает только ОДИН. Вы не получите никаких предупреждений или даже намеков на любые другие помощники, которые могут быть скрыты».
blockquote>http://davidglassborow.blogspot.com/2006/05/class-helpers-good-or-bad.html
В первый раз, когда я помню, что вы испытывали то, что вы называете «помощниками класса», было во время обучения Objective C. Cocoa (инфраструктура Objective C от Apple) использует так называемые «Категории».
Категория позволяет вам для расширения существующего класса путем добавления собственных методов без подкласса. На самом деле Cocoa поощряет вас избегать подкласса, когда это возможно. Часто это имеет смысл для подкласса, но часто его можно избежать с помощью категорий.
. Хорошим примером использования категории в какао является так называемый «код ключевых значений (KVC)» и «Key Value Observing (KVO). "
Эта система реализована с использованием двух категорий (NSKeyValueCoding и NSKeyValueObserving). Эти категории определяют и реализуют методы, которые могут быть добавлены в любой класс, который вы хотите. Например, Cocoa добавляет «соответствие» KVC / KVO, используя эти категории для добавления методов в NSArray, таких как:
- (id)valueForKey:(NSString *)key
Класс NSArray не имеет ни объявления, ни реализации этого метода. Однако, используя категорию. Вы можете вызвать этот метод для любого класса NSArray. Вам не требуется подкласс NSArray для получения соответствия KVC / KVO.
NSArray *myArray = [NSArray array]; // Make a new empty array
id myValue = [myArray valueForKey:@"name"]; // Call a method defined in the category
Используя этот метод, вы легко можете добавить поддержку KVC / KVO в свои собственные классы. Интерфейсы Java позволяют добавлять декларации методов, но категории позволяют также добавлять фактические реализации в существующие классы.
Сначала я был скептически относился к помощникам класса. Но затем я прочитал интересную запись в блоге , и теперь я убежден, что они действительно полезны.
Например, если вы хотите получить дополнительную функциональность для существующего класса экземпляра и для некоторых причина, по которой вы не можете изменить существующий источник. Вы можете создать помощник класса, чтобы добавить эту функциональность.
Пример:
type
TStringsHelper = class helper for TStrings
public
function IsEmpty: Boolean;
end;
function TStringsHelper.IsEmpty: Boolean;
begin
Result := Count = 0;
end;
Каждый раз мы теперь используем экземпляр (подкласс) TStrings и TStringsHelper находится в пределах области действия. У нас есть доступ к методу IsEmpty.
Пример:
procedure TForm1.Button1Click(Sender: TObject);
begin
if Memo1.Lines.IsEmpty then
Button1.Caption := 'Empty'
else
Button1.Caption := 'Filled';
end;
Примечания:
Как показывает GameCat, TStrings - хороший кандидат, чтобы избежать ввода:
type
TMyObject = class
public
procedure DoSomething;
end;
TMyObjectStringsHelper = class helper for TStrings
private
function GetMyObject(const Name: string): TMyObject;
procedure SetMyObject(const Name: string; const Value: TMyObject);
public
property MyObject[const Name: string]: TMyObject read GetMyObject write SetMyObject; default;
end;
function TMyObjectStringsHelper.GetMyObject(const Name: string): TMyObject;
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
result := nil
else
result := Objects[idx] as TMyObject;
end;
procedure TMyObjectStringsHelper.SetMyObject(const Name: string; const Value:
TMyObject);
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
AddObject(Name, Value)
else
Objects[idx] := Value;
end;
var
lst: TStrings;
begin
...
lst['MyName'] := TMyObject.Create;
...
lst['MyName'].DoSomething;
...
end;
Вам когда-нибудь приходилось обращаться к многострочным строкам в реестре?
type
TRegistryHelper = class helper for TRegistry
public
function ReadStrings(const ValueName: string): TStringDynArray;
end;
function TRegistryHelper.ReadStrings(const ValueName: string): TStringDynArray;
var
DataType: DWord;
DataSize: DWord;
Buf: PChar;
P: PChar;
Len: Integer;
I: Integer;
begin
result := nil;
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, nil, @DataSize) = ERROR_SUCCESS then begin
if DataType = REG_MULTI_SZ then begin
GetMem(Buf, DataSize + 2);
try
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, PByte(Buf), @DataSize) = ERROR_SUCCESS then begin
for I := 0 to 1 do begin
if Buf[DataSize - 2] <> #0 then begin
Buf[DataSize] := #0;
Inc(DataSize);
end;
end;
Len := 0;
for I := 0 to DataSize - 1 do
if Buf[I] = #0 then
Inc(Len);
Dec(Len);
if Len > 0 then begin
SetLength(result, Len);
P := Buf;
for I := 0 to Len - 1 do begin
result[I] := StrPas(P);
Inc(P, Length(P) + 1);
end;
end;
end;
finally
FreeMem(Buf, DataSize);
end;
end;
end;
end;