Открыть боковую панель
Simple-Developing
Otus-Csharp
Unmanaged
Коммиты
a5c6b847
Коммит
a5c6b847
создал
Фев 03, 2026
по автору
Charles Williams
Просмотр файлов
Load sources.
владелец
29ff9df6
Изменения
34
Скрыть пробелы
Построчно
Рядом
.gitattributes
0 → 100644
Просмотр файла @
a5c6b847
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
Disposable/AsyncResourceHolder.cs
0 → 100644
Просмотр файла @
a5c6b847
namespace
Disposable
{
public
class
AsyncResourceHolder
:
IAsyncDisposable
{
private
bool
_connectionOpen
=
false
;
private
int
_id
;
private
static
int
_nextId
=
1
;
public
AsyncResourceHolder
()
{
_id
=
_nextId
++;
Console
.
WriteLine
(
$"[Async#
{
_id
}
] Initializing connection synchronously (for demo simplicity)..."
);
// В реальном коде, открытие тоже могло бы быть асинхронным,
// но для демонстрации DisposeAsync, сделаем конструктор синхронным.
_connectionOpen
=
true
;
Console
.
WriteLine
(
$"[Async#
{
_id
}
] Connection
{
_id
}
opened."
);
}
public
async
Task
SendDataAsync
(
string
data
)
{
if
(!
_connectionOpen
)
throw
new
InvalidOperationException
(
$"[Async#
{
_id
}
] Connection not open or disposed."
);
Console
.
WriteLine
(
$"[Async#
{
_id
}
] Sending data asynchronously: '
{
data
}
'..."
);
await
Task
.
Delay
(
500
);
// Имитация асинхронной отправки данных
Console
.
WriteLine
(
$"[Async#
{
_id
}
] Data '
{
data
}
' sent."
);
}
// Реализация IAsyncDisposable
public
async
ValueTask
DisposeAsync
()
{
Console
.
WriteLine
(
$"[Async#
{
_id
}
] DisposeAsync() called. Closing connection asynchronously..."
);
if
(
_connectionOpen
)
{
await
Task
.
Delay
(
750
);
// Имитация асинхронного закрытия/очистки
_connectionOpen
=
false
;
Console
.
WriteLine
(
$"[Async#
{
_id
}
] Connection
{
_id
}
closed asynchronously."
);
}
else
{
Console
.
WriteLine
(
$"[Async#
{
_id
}
] Connection
{
_id
}
was already closed."
);
}
}
// Опционально: Реализация IDisposable для совместимости
// Если вы реализуете оба интерфейса, Dispose() должен быть осторожен.
// Часто он просто вызывает DisposeAsync().AsTask().Wait() или AsTask().GetAwaiter().GetResult(),
// что, как мы знаем, может привести к дедлокам.
// Если ваш ресурс требует асинхронной очистки, лучше избегать синхронного Dispose()
// или по крайней мере ясно документировать его ограничения.
// public void Dispose()
// {
// Console.WriteLine($"[Async#{_id}] Synchronous Dispose() called. Waiting for async cleanup...");
// DisposeAsync().AsTask().Wait(); // Блокировка!
// GC.SuppressFinalize(this);
// Console.WriteLine($"[Async#{_id}] Synchronous Dispose() finished.");
// }
}
}
Disposable/BadResourceHolder.cs
0 → 100644
Просмотр файла @
a5c6b847
namespace
Disposable
{
public
class
BadResourceHolder
{
private
FileStream
_file
;
private
string
_filePath
;
public
BadResourceHolder
(
string
filePath
)
{
_filePath
=
filePath
;
_file
=
new
FileStream
(
filePath
,
FileMode
.
OpenOrCreate
,
FileAccess
.
ReadWrite
);
Console
.
WriteLine
(
$"[Bad] Resource acquired:
{
_filePath
}
"
);
}
public
void
WriteSomeData
(
string
data
)
{
byte
[]
bytes
=
System
.
Text
.
Encoding
.
UTF8
.
GetBytes
(
data
+
Environment
.
NewLine
);
_file
.
Write
(
bytes
,
0
,
bytes
.
Length
);
Console
.
WriteLine
(
$"[Bad] Data written to
{
_filePath
}
"
);
}
// Здесь нет Dispose() и нет финализатора.
// Файл будет закрыт только при завершении приложения или когда GC по какой-то причине решит,
// что FileStream больше не нужен, но это будет очень недетерминированно.
}
}
Disposable/Disposable.csproj
0 → 100644
Просмотр файла @
a5c6b847
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Disposable/DisposableStruct.cs
0 → 100644
Просмотр файла @
a5c6b847
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Text
;
using
System.Threading.Tasks
;
namespace
Disposable
{
public
class
SomeClass
:
IDisposable
{
private
int
_counter
=
0
;
private
bool
_isDisposed
=
false
;
private
readonly
Lock
_balanceLock
=
new
();
public
void
DoWork
()
{
if
(
_isDisposed
)
{
throw
new
ObjectDisposedException
(
nameof
(
SomeClass
));
}
using
(
_balanceLock
.
EnterScope
())
{
_counter
++;
Console
.
WriteLine
(
$"DoWork — Counter:
{
_counter
}
"
);
}
}
public
void
Dispose
()
{
if
(!
_isDisposed
)
{
Console
.
WriteLine
(
"SomeClass is being disposed"
);
_isDisposed
=
true
;
}
}
}
public
struct
SomeStruct
:
IDisposable
{
public
SomeClass
ObjectToDispose
{
get
;
set
;
}
public
void
Dispose
()
{
Console
.
WriteLine
(
$"
{
nameof
(
SomeStruct
)}
is being disposed"
);
ObjectToDispose
.
Dispose
();
}
}
}
Disposable/FinalizeOrNot.cs
0 → 100644
Просмотр файла @
a5c6b847
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Text
;
using
System.Threading.Tasks
;
namespace
Disposable
{
public
class
ClassWithFinalyzer
:
IDisposable
{
private
int
_counter
=
0
;
private
readonly
int
_someData1
;
private
readonly
int
_someData2
;
private
bool
_isDisposed
=
false
;
public
ClassWithFinalyzer
(
bool
isGood
=
true
)
{
Console
.
WriteLine
(
"Constructor is called"
);
_someData1
=
100
;
if
(!
isGood
)
throw
new
InvalidOperationException
(
"Crash"
);
_someData2
=
_someData1
+
100
;
}
public
void
DoWork
()
{
if
(
_isDisposed
)
{
throw
new
ObjectDisposedException
(
nameof
(
ClassWithFinalyzer
));
}
_counter
++;
Console
.
WriteLine
(
$"Do work — Counter:
{
_counter
}
"
);
}
~
ClassWithFinalyzer
()
{
Console
.
WriteLine
(
"Finalyzer is called"
);
Dispose
(
false
);
}
public
void
Dispose
()
{
Dispose
(
true
);
GC
.
SuppressFinalize
(
this
);
Console
.
WriteLine
(
"SuppressFinalize is called"
);
}
protected
virtual
void
Dispose
(
bool
manual
)
{
Console
.
WriteLine
(
"Dispose is called"
);
if
(!
_isDisposed
)
{
if
(
manual
)
{
// Здесь освобождаем управляемые ресурсы, например, можно вызвать методы
// Dispose внутренних объектов класса.
Console
.
WriteLine
(
"Free some managed resources..."
);
}
// Дальше освобождаем неуправляемые ресурсы.
Console
.
WriteLine
(
"SomeClass is being disposed with state {0}"
,
_someData2
);
_isDisposed
=
true
;
}
}
}
}
Disposable/GoodResourceHolder.cs
0 → 100644
Просмотр файла @
a5c6b847
namespace
Disposable
{
public
class
GoodResourceHolder
:
IDisposable
{
private
FileStream
_file
;
private
string
_filePath
;
private
bool
_disposed
=
false
;
public
GoodResourceHolder
(
string
filePath
)
{
_filePath
=
filePath
;
_file
=
new
FileStream
(
filePath
,
FileMode
.
OpenOrCreate
,
FileAccess
.
ReadWrite
);
Console
.
WriteLine
(
$"[Good] Resource acquired:
{
_filePath
}
"
);
}
public
void
WriteSomeData
(
string
data
)
{
if
(
_disposed
)
throw
new
ObjectDisposedException
(
nameof
(
GoodResourceHolder
),
"Cannot write to a disposed resource."
);
byte
[]
bytes
=
System
.
Text
.
Encoding
.
UTF8
.
GetBytes
(
data
+
Environment
.
NewLine
);
_file
.
Write
(
bytes
,
0
,
bytes
.
Length
);
Console
.
WriteLine
(
$"[Good] Data written to
{
_filePath
}
"
);
}
// Публичный метод Dispose, вызываемый пользователем или using
public
void
Dispose
()
{
Dispose
(
true
);
// Передаем, что вызван явно (disposing = true)
GC
.
SuppressFinalize
(
this
);
// Говорим GC не вызывать финализатор, т.к. мы уже очистились
Console
.
WriteLine
(
$"[Good] Dispose() called for
{
_filePath
}
"
);
}
// Защищенный виртуальный метод для фактической очистки
protected
virtual
void
Dispose
(
bool
disposing
)
{
if
(!
_disposed
)
{
if
(
disposing
)
{
// Освобождаем управляемые ресурсы (в данном случае, FileStream)
_file
?.
Dispose
();
Console
.
WriteLine
(
$"[Good] Managed resource (FileStream) disposed for
{
_filePath
}
."
);
}
// Здесь можно было бы освободить неуправляемые ресурсы напрямую,
// но FileStream сам заботится о своем нативном дескрипторе.
// В следующих секциях увидим прямую работу с неуправляемыми ресурсами.
_disposed
=
true
;
}
}
// Финализатор (деструктор) - как страховка, если Dispose не вызван явно.
// Обсудим подробнее в следующей секции.
~
GoodResourceHolder
()
{
Console
.
WriteLine
(
$"[Good] Finalizer called for
{
_filePath
}
. Dispose(false)"
);
Dispose
(
false
);
// Передаем, что вызван финализатором (disposing = false)
}
}
}
Disposable/Program.cs
0 → 100644
Просмотр файла @
a5c6b847
namespace
Disposable
{
internal
class
Program
{
static
async
Task
Main
(
string
[]
args
)
{
Console
.
WriteLine
(
"--- Демонстрация 1: Утечка без IDisposable ---"
);
string
badFilePath
=
"bad_log.txt"
;
try
{
BadResourceHolder
badHolder
=
new
BadResourceHolder
(
badFilePath
);
badHolder
.
WriteSomeData
(
"This data should be in bad_log.txt"
);
// Здесь мы забываем закрыть файл!
// badHolder._file.Close(); // Если бы мы это сделали, утечки бы не было.
Console
.
WriteLine
(
$"[Bad] File '
{
badFilePath
}
' is theoretically still open."
);
// Попробуйте удалить bad_log.txt вручную. Если он еще открыт, получите ошибку.
System
.
IO
.
File
.
Delete
(
badFilePath
);
// Вызовет ошибку, если файл еще открыт
}
catch
(
Exception
ex
)
{
Console
.
WriteLine
(
$"[Error in BadResourceHolder demo]:
{
ex
.
Message
}
"
);
Console
.
WriteLine
(
"This error indicates the file might still be in use."
);
}
Console
.
WriteLine
(
"-------------------------------------------\n"
);
System
.
Threading
.
Thread
.
Sleep
(
500
);
// Дадим немного времени
// --- Демонстрация 2: Корректное освобождение с IDisposable и using ---
Console
.
WriteLine
(
"--- Демонстрация 2: Корректное освобождение с IDisposable и using ---"
);
string
goodFilePath
=
"good_log.txt"
;
try
{
using
(
var
goodHolder
=
new
GoodResourceHolder
(
goodFilePath
))
{
goodHolder
.
WriteSomeData
(
"This data should be in good_log.txt"
);
Console
.
WriteLine
(
$"[Good] File '
{
goodFilePath
}
' is in use within using block."
);
}
// <-- Здесь goodHolder.Dispose() вызывается автоматически
Console
.
WriteLine
(
$"[Good] File '
{
goodFilePath
}
' is now closed. Trying to delete..."
);
System
.
IO
.
File
.
Delete
(
goodFilePath
);
// Теперь это должно сработать!
Console
.
WriteLine
(
$"[Good] File '
{
goodFilePath
}
' successfully deleted."
);
}
catch
(
Exception
ex
)
{
Console
.
WriteLine
(
$"[Error in GoodResourceHolder demo]:
{
ex
.
Message
}
"
);
}
Console
.
WriteLine
(
"-------------------------------------------\n"
);
// --- Демонстрация 3: using декларация (C# 8.0+) ---
Console
.
WriteLine
(
"--- Демонстрация 3: using декларация (C# 8.0+) ---"
);
string
anotherFilePath
=
"another_log.txt"
;
Demo3
(
anotherFilePath
);
Console
.
ReadKey
();
// Ждем, чтобы показать, что файл `another_log.txt` будет удален после выхода из Main()
System
.
IO
.
File
.
Delete
(
anotherFilePath
);
Console
.
WriteLine
(
$"[Another] File '
{
anotherFilePath
}
' deleted upon exit."
);
Console
.
WriteLine
(
"-------------------------------------------\n"
);
// --- Демонстрация: Disposable struct
Console
.
WriteLine
(
"--- Демонстрация: Disposable struct"
);
DisposableClassAndStruct
();
Console
.
ReadKey
();
//return;
Console
.
WriteLine
(
"-------------------------------------------\n"
);
// --- Демонстрация: Finalize or not finalize
Console
.
WriteLine
(
"--- Демонстрация: Finalize or not finalize"
);
FinalizeOrNot
(
isDirectExecution
:
true
,
isGood
:
true
);
Console
.
ReadKey
();
//return
Console
.
WriteLine
(
"-------------------------------------------\n"
);
// --- Демонстрация: Restore Object
Console
.
WriteLine
(
"--- Демонстрация: Restore Object"
);
RestoreObject
(
isForRestore
:
true
);
Console
.
ReadKey
();
//return;
Console
.
WriteLine
(
"-------------------------------------------\n"
);
Console
.
WriteLine
(
"--- Демонстрация 4: Асинхронное освобождение с IAsyncDisposable и await using ---"
);
Console
.
WriteLine
(
"Начинаем использование AsyncResourceHolder..."
);
try
{
await
using
(
var
asyncHolder
=
new
AsyncResourceHolder
())
// <-- Здесь await using
{
await
asyncHolder
.
SendDataAsync
(
"First message"
);
await
asyncHolder
.
SendDataAsync
(
"Second message"
);
Console
.
WriteLine
(
"[Async Demo] Inside await using block. Resource is active."
);
// asyncHolder.DisposeAsync() будет вызван автоматически здесь.
}
Console
.
WriteLine
(
"[Async Demo] Outside await using block. Resource should be closed."
);
}
catch
(
Exception
ex
)
{
Console
.
WriteLine
(
$"[Async Demo Error]:
{
ex
.
Message
}
"
);
}
Console
.
WriteLine
(
"--- Демонстрация 4 завершена ---\n"
);
// Пример использования с множественными await using декларациями
Console
.
WriteLine
(
"--- Демонстрация 5: Множественные await using декларации ---"
);
await
using
var
asyncHolder1
=
new
AsyncResourceHolder
();
await
using
var
asyncHolder2
=
new
AsyncResourceHolder
();
await
asyncHolder1
.
SendDataAsync
(
"From holder 1"
);
await
asyncHolder2
.
SendDataAsync
(
"From holder 2"
);
Console
.
WriteLine
(
"[Async Demo Multiple] All holders active. DisposeAsync will be called in reverse order."
);
// asyncHolder2.DisposeAsync() будет вызван первым, затем asyncHolder1.DisposeAsync()
// при выходе из метода Main.
Console
.
WriteLine
(
"\nНажмите любую клавишу для завершения программы..."
);
Console
.
ReadKey
();
Console
.
WriteLine
(
"Программа завершается."
);
// Так как asyncHolder1 и asyncHolder2 объявлены как using var,
// DisposeAsync() для них будет вызван после этой точки при выходе из Main.
// В идеальном мире, мы всегда используем using
using
(
var
holder
=
new
SafeResourceHolder
())
{
// Работаем с ресурсом
Console
.
WriteLine
(
"Working with resource..."
);
}
// Здесь автоматически вызывается Dispose(), и финализатор не нужен.
}
private
static
void
Demo3
(
string
anotherFilePath
)
{
using
var
anotherHolder
=
new
GoodResourceHolder
(
anotherFilePath
);
anotherHolder
.
WriteSomeData
(
"This is from a using declaration."
);
Console
.
WriteLine
(
$"[Another] File '
{
anotherFilePath
}
' is in use."
);
// В конце метода Main() `anotherHolder.Dispose()` будет вызван автоматически.
// Но для демонстрации удаления, мы можем вызвать GC.Collect() (для примера, в реальном коде так не делать)
Console
.
WriteLine
(
$"[Another] Will attempt to delete '
{
anotherFilePath
}
' after Main completes."
);
// Не удаляйте здесь, так как using декларация еще не завершилась.
// Для полной уверенности в очистке после using декларации
// System.GC.Collect();
// System.GC.WaitForPendingFinalizers();
// System.IO.File.Delete(anotherFilePath); // Это сработает после завершения метода
}
private
static
void
DisposableClassAndStruct
()
{
Console
.
WriteLine
(
"Create SomeStruct"
);
SomeStruct
someStruct
=
new
SomeStruct
()
{
ObjectToDispose
=
new
SomeClass
()
};
try
{
someStruct
.
ObjectToDispose
.
DoWork
();
someStruct
.
Dispose
();
someStruct
.
ObjectToDispose
.
DoWork
();
}
catch
(
Exception
ex
)
{
Console
.
WriteLine
(
$"
{
ex
.
GetType
().
Name
}
:
{
ex
.
Message
}
"
);
}
Console
.
WriteLine
(
"Ok"
);
}
private
static
void
FinalizeOrNot
(
bool
isDirectExecution
,
bool
isGood
)
{
Console
.
WriteLine
(
"Direct execution: {0} with Error in Constructor: {1}"
,
isDirectExecution
?
"Yes"
:
"No"
,
isGood
?
"No"
:
"Yes"
);
if
(
isDirectExecution
)
{
try
{
using
ClassWithFinalyzer
obj
=
new
(
isGood
);
obj
.
DoWork
();
}
catch
(
Exception
ex
)
{
Console
.
WriteLine
(
"Error: {0}"
,
ex
.
Message
);
}
}
else
{
SomeCall
(
isGood
);
}
GC
.
Collect
();
GC
.
WaitForPendingFinalizers
();
Console
.
WriteLine
(
"End"
);
// Additional Definition:
static
void
SomeCall
(
bool
isGood
)
{
try
{
using
ClassWithFinalyzer
obj
=
new
(
isGood
);
obj
.
DoWork
();
}
catch
(
Exception
ex
)
{
Console
.
WriteLine
(
"Error: {0}"
,
ex
.
Message
);
}
}
}
static
void
RestoreObject
(
bool
isForRestore
)
{
Console
.
WriteLine
(
"Mode for restore: {0}"
,
isForRestore
);
CallWithRestoring
(
isForRestore
,
"'Test Object'"
);
GC
.
Collect
();
GC
.
WaitForPendingFinalizers
();
bool
isRestored
=
TestRestored
();
if
(
isRestored
)
{
Console
.
WriteLine
(
"Delete the object again."
);
ClassWithRestoring
.
ToDelete
();
GC
.
Collect
();
GC
.
WaitForPendingFinalizers
();
_
=
TestRestored
();
}
Console
.
WriteLine
(
"End"
);
// Additional Definitions:
static
void
CallWithRestoring
(
bool
isForRestore
,
string
parameter
)
{
ClassWithRestoring
obj
=
new
(
isForRestore
,
parameter
);
}
static
bool
TestRestored
()
{
bool
isRestored
=
ClassWithRestoring
.
IsRestored
();
Console
.
WriteLine
(
"We have restored object: {0}"
,
isRestored
?
"Yes"
:
"No"
);
if
(
isRestored
)
{
Console
.
WriteLine
(
"We have the object {0}."
,
ClassWithRestoring
.
GetRestored
()?.
Name
);
}
return
isRestored
;
}
}
}
}
Disposable/RestoreObject.cs
0 → 100644
Просмотр файла @
a5c6b847
using
System
;
using
System.Collections.Concurrent
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Text
;
using
System.Threading.Tasks
;
namespace
Disposable
{
public
sealed
class
ClassWithRestoring
{
public
static
void
ToDelete
()
=>
_restoredQueue
.
Clear
();
public
static
bool
IsRestored
()
=>
_restoredQueue
.
Count
>
0
;
public
static
ClassWithRestoring
?
GetRestored
()
{
if
(
_restoredQueue
.
TryDequeue
(
out
object
?
result
))
{
return
(
ClassWithRestoring
)
result
;
}
return
null
;
}
private
static
readonly
ConcurrentQueue
<
object
>
_restoredQueue
=
new
();
private
bool
_isForRestore
;
public
string
Name
{
get
;
private
set
;
}
public
ClassWithRestoring
(
bool
isForRestore
=
false
,
string
name
=
""
)
{
_isForRestore
=
isForRestore
;
Name
=
name
;
Console
.
WriteLine
(
"Constructor is called, we planned to restore: {0} for this instance: {1}"
,
isForRestore
?
"Yes"
:
"No"
,
name
);
}
~
ClassWithRestoring
()
{
Console
.
WriteLine
(
"Finalyzer is called"
);
if
(
_isForRestore
)
{
Console
.
WriteLine
(
"Restoring object"
);
// Восстанавливаем объект (разово).
_isForRestore
=
false
;
// ConcurrentQueue, потому что финализатор вызывается из отдельного потока.
_restoredQueue
.
Enqueue
(
this
);
// Чтобы финализатор выполнился повторно при следующем удалении объекта
// Для каждого восстановления нужно вызвать только один раз.
GC
.
ReRegisterForFinalize
(
this
);
}
else
{
Console
.
WriteLine
(
"Finish Finalyzer"
);
}
}
}
}
Disposable/SafeResourceHolder .cs
0 → 100644
Просмотр файла @
a5c6b847
namespace
Disposable
{
public
class
SafeResourceHolder
:
IDisposable
{
private
IntPtr
_unmanagedHandle
;
// Неуправляемый ресурс
private
bool
_disposed
=
false
;
public
SafeResourceHolder
()
{
_unmanagedHandle
=
OpenSomeNativeResource
();
Console
.
WriteLine
(
$"MySafeResourceHolder created. Handle:
{
_unmanagedHandle
}
"
);
}
public
void
Dispose
()
{
Dispose
(
true
);
GC
.
SuppressFinalize
(
this
);
// Очень важно!
}
protected
virtual
void
Dispose
(
bool
disposing
)
{
if
(!
_disposed
)
{
if
(
disposing
)
{
// Освобождение управляемых ресурсов, если они есть
Console
.
WriteLine
(
"Disposing managed resources (if any)."
);
}
// Освобождение неуправляемых ресурсов
Console
.
WriteLine
(
$"Releasing unmanaged handle:
{
_unmanagedHandle
}
"
);
CloseSomeNativeResource
(
_unmanagedHandle
);
_unmanagedHandle
=
IntPtr
.
Zero
;
// Обнуляем, чтобы избежать повторного использования
_disposed
=
true
;
}
}
// Финализатор как страховка!
~
SafeResourceHolder
()
{
Console
.
WriteLine
(
$"MySafeResourceHolder finalizer called as a fallback. Releasing handle:
{
_unmanagedHandle
}
"
);
Dispose
(
false
);
// Вызываем Dispose с параметром false, так как GC вызвал финализатор
}
private
IntPtr
OpenSomeNativeResource
()
=>
new
IntPtr
(
456
);
private
void
CloseSomeNativeResource
(
IntPtr
handle
)
=>
Console
.
WriteLine
(
$"Native resource
{
handle
}
closed by Dispose."
);
}
}
InvalidHandle/InvalidHandle.csproj
0 → 100644
Просмотр файла @
a5c6b847
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
InvalidHandle/Program.cs
0 → 100644
Просмотр файла @
a5c6b847
using
System.Runtime.InteropServices
;
namespace
InvalidHandle
{
internal
class
Program
{
[
DllImport
(
"kernel32.dll"
,
SetLastError
=
true
)]
public
static
extern
IntPtr
CreateFile
(
string
lpFileName
,
uint
dwDesiredAccess
,
uint
dwShareMode
,
IntPtr
lpSecurityAttributes
,
// IntPtr.Zero для NULL
uint
dwCreationDisposition
,
uint
dwFlagsAndAttributes
,
IntPtr
hTemplateFile
);
// IntPtr.Zero для NULL
[
DllImport
(
"kernel32.dll"
,
SetLastError
=
true
)]
public
static
extern
bool
CloseHandle
(
IntPtr
hObject
);
public
const
uint
GENERIC_READ
=
0x80000000
;
public
const
uint
OPEN_EXISTING
=
3
;
public
static
readonly
IntPtr
INVALID_HANDLE_VALUE
=
new
IntPtr
(-
1
);
static
void
Main
(
string
[]
args
)
{
DemonstrateInvalidHandle
();
}
public
static
void
DemonstrateInvalidHandle
()
{
// Попытка открыть несуществующий файл
IntPtr
fileHandle
=
CreateFile
(
"non_existent_file.txt"
,
GENERIC_READ
,
0
,
IntPtr
.
Zero
,
OPEN_EXISTING
,
0
,
IntPtr
.
Zero
);
if
(
fileHandle
==
INVALID_HANDLE_VALUE
)
{
Console
.
WriteLine
(
"Failed to open file. Handle is INVALID_HANDLE_VALUE."
);
// Отсутствие проверки здесь приведет к ошибке при попытке использовать fileHandle
}
else
{
Console
.
WriteLine
(
$"Successfully opened file. Handle:
{
fileHandle
}
"
);
CloseHandle
(
fileHandle
);
}
}
public
class
LeakyFileHandle
// АНТИПАТТЕРН!
{
private
IntPtr
_fileHandle
;
public
LeakyFileHandle
(
string
filePath
)
{
_fileHandle
=
CreateFile
(
filePath
,
GENERIC_READ
,
0
,
IntPtr
.
Zero
,
OPEN_EXISTING
,
0
,
IntPtr
.
Zero
);
if
(
_fileHandle
==
INVALID_HANDLE_VALUE
)
{
throw
new
System
.
ComponentModel
.
Win32Exception
(
Marshal
.
GetLastWin32Error
());
}
Console
.
WriteLine
(
$"LeakyFileHandle created. Handle:
{
_fileHandle
}
"
);
}
// Нет Dispose, нет финализатора -> утечка!
// ~LeakyFileHandle() { CloseHandle(_fileHandle); } // Это нужно, но лучше IDisposable
}
// new LeakyFileHandle("some_file.txt"); // Ресурс будет утекать
public
static
void
DemonstrateDoubleFree
()
{
IntPtr
tempHandle
=
CreateFile
(
"test_file.txt"
,
GENERIC_READ
,
0
,
IntPtr
.
Zero
,
OPEN_EXISTING
,
0
,
IntPtr
.
Zero
);
if
(
tempHandle
==
INVALID_HANDLE_VALUE
)
{
Console
.
WriteLine
(
"Could not open test_file.txt for double-free demo."
);
return
;
}
Console
.
WriteLine
(
$"Opened test_file.txt. Handle:
{
tempHandle
}
"
);
CloseHandle
(
tempHandle
);
// Первое освобождение
Console
.
WriteLine
(
$"First close successful for handle:
{
tempHandle
}
"
);
// Попытка использовать tempHandle после освобождения
// Это может привести к Use-After-Free или просто ошибке
// Например, ReadFile(tempHandle, ...)
CloseHandle
(
tempHandle
);
// Второе освобождение (Double-Free) - опасно!
Console
.
WriteLine
(
$"Second close attempted for handle:
{
tempHandle
}
. This is a double-free risk!"
);
}
}
}
NativeBufferHandle/NativeBufferHandle.cs
0 → 100644
Просмотр файла @
a5c6b847
using
System.Runtime.InteropServices
;
namespace
NativeBufferHandle
{
public
class
NativeBufferHandle
:
SafeHandle
{
public
NativeBufferHandle
()
:
base
(
IntPtr
.
Zero
,
true
)
{
}
// Конструктор, который принимает уже выделенный указатель
public
NativeBufferHandle
(
IntPtr
preAllocatedPtr
,
bool
ownsHandle
=
true
)
:
base
(
IntPtr
.
Zero
,
ownsHandle
)
{
SetHandle
(
preAllocatedPtr
);
}
public
override
bool
IsInvalid
=>
handle
==
IntPtr
.
Zero
;
// Этот метод будет вызван при освобождении SafeHandle (например, при сборке мусора)
protected
override
bool
ReleaseHandle
()
{
if
(!
IsInvalid
)
{
Console
.
WriteLine
(
$"Освобождаем неуправляемый буфер по адресу:
{
handle
}
"
);
Marshal
.
FreeHGlobal
(
handle
);
// Используем Marshal.FreeHGlobal для освобождения памяти, выделенной через Marshal.AllocHGlobal
handle
=
IntPtr
.
Zero
;
return
true
;
}
return
false
;
}
// Для демонстрации, можем добавить свойство для размера буфера
public
long
Size
{
get
;
set
;
}
}
}
NativeBufferHandle/NativeBufferHandle.csproj
0 → 100644
Просмотр файла @
a5c6b847
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
NativeBufferHandle/Program.cs
0 → 100644
Просмотр файла @
a5c6b847
using
System.Runtime.InteropServices
;
using
System.Text
;
namespace
NativeBufferHandle
{
internal
class
Program
{
static
void
Main
(
string
[]
args
)
{
string
message
=
"Привет, мир! This is a test string."
;
byte
[]
messageBytes
=
Encoding
.
UTF8
.
GetBytes
(
message
);
long
bufferSize
=
messageBytes
.
Length
+
1
;
// +1 для нулевого символа в конце (C-style строка)
// Используем using для гарантированного освобождения SafeHandle
using
(
NativeBufferHandle
bufferHandle
=
new
NativeBufferHandle
())
{
try
{
// 2. Выделяем неуправляемую память с помощью Marshal.AllocHGlobal
// В реальных сценариях вы могли бы использовать P/Invoke для вызова нативных функций выделения памяти
IntPtr
nativeBufferPtr
=
Marshal
.
AllocHGlobal
((
IntPtr
)
bufferSize
);
bufferHandle
.
SetHandle
(
nativeBufferPtr
);
bufferHandle
.
Size
=
bufferSize
;
Console
.
WriteLine
(
$"Выделен неуправляемый буфер по адресу:
{
bufferHandle
.
handle
}
размером:
{
bufferHandle
.
Size
}
байт."
);
// 3. Записываем данные в неуправляемую память с помощью NativeMemory
unsafe
{
byte
*
pBuffer
=
(
byte
*)
bufferHandle
.
handle
;
// Копируем байты сообщения
NativeMemory
.
Copy
(
messageBytes
.
AsSpan
(),
pBuffer
,
(
nuint
)
messageBytes
.
Length
);
// Добавляем нулевой символ в конец (опционально, но хорошая практика для строк)
pBuffer
[
messageBytes
.
Length
]
=
0
;
Console
.
WriteLine
(
"Данные записаны в неуправляемый буфер."
);
// 4. Читаем данные из неуправляемой памяти с помощью NativeMemory
Span
<
byte
>
readBuffer
=
stackalloc
byte
[(
int
)
bufferSize
];
NativeMemory
.
Copy
(
pBuffer
,
readBuffer
,
(
nuint
)
bufferSize
);
// Находим конец строки (нулевой символ)
int
nullTerminatorIndex
=
readBuffer
.
IndexOf
((
byte
)
0
);
if
(
nullTerminatorIndex
!=
-
1
)
{
readBuffer
=
readBuffer
.
Slice
(
0
,
nullTerminatorIndex
);
}
string
readMessage
=
Encoding
.
UTF8
.
GetString
(
readBuffer
);
Console
.
WriteLine
(
$"Прочитанная строка из неуправляемого буфера: \"
{
readMessage
}
\""
);
}
}
catch
(
Exception
ex
)
{
Console
.
WriteLine
(
$"Произошла ошибка:
{
ex
.
Message
}
"
);
}
}
// Здесь SafeHandle будет автоматически освобожден, даже если произошла ошибка
Console
.
WriteLine
(
"Приложение завершено. SafeHandle должен был быть освобожден."
);
Console
.
ReadKey
();
}
}
}
NativeHandles/NativeHandles.csproj
0 → 100644
Просмотр файла @
a5c6b847
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
NativeHandles/Program.cs
0 → 100644
Просмотр файла @
a5c6b847
using
System.Runtime.InteropServices
;
namespace
NativeHandles
{
public
class
NativeHandles
{
// [DllImport] для GetCurrentProcess - возвращает псевдо-дескриптор текущего процесса
[
DllImport
(
"kernel32.dll"
,
SetLastError
=
true
)]
public
static
extern
IntPtr
GetCurrentProcess
();
// Функция для закрытия дескриптора (для реальных дескрипторов,
// GetCurrentProcess возвращает псевдо-дескриптор, который не нужно закрывать)
// Эта функция будет использоваться в следующих примерах для реальных хэндлов
[
DllImport
(
"kernel32.dll"
,
SetLastError
=
true
)]
[
return
:
MarshalAs
(
UnmanagedType
.
Bool
)]
public
static
extern
bool
CloseHandle
(
IntPtr
hObject
);
public
static
void
Main
(
string
[]
args
)
{
IntPtr
processHandle
=
IntPtr
.
Zero
;
// Инициализируем нулевым значением
try
{
processHandle
=
GetCurrentProcess
();
Console
.
WriteLine
(
$"Current process handle (pseudo-handle):
{
processHandle
}
"
);
// Важно: GetCurrentProcess возвращает псевдо-дескриптор, который не нужно закрывать.
// Если бы это был реальный дескриптор, полученный, например, от CreateFile,
// мы бы вызвали CloseHandle(processHandle);
}
finally
{
// В случае реального дескриптора:
// if (processHandle != IntPtr.Zero && IsRealHandle(processHandle))
// {
// CloseHandle(processHandle);
// Console.WriteLine($"Closed handle: {processHandle}");
// }
}
}
}
}
NativeInvoke/NativeInvoke.csproj
0 → 100644
Просмотр файла @
a5c6b847
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
NativeInvoke/Program.cs
0 → 100644
Просмотр файла @
a5c6b847
using
System.Runtime.InteropServices
;
namespace
NativeInvoke
{
public
class
NativeMethods
{
// [DllImport] атрибут указывает, из какой DLL мы импортируем функцию.
// "kernel32.dll" - это одна из основных системных библиотек Windows.
// Метод должен быть статическим и extern (реализация находится вовне).
[
DllImport
(
"kernel32.dll"
,
CharSet
=
CharSet
.
Unicode
,
SetLastError
=
true
)]
public
static
extern
void
OutputDebugString
(
string
lpOutputString
);
// Другой пример: получение текущего системного времени
[
DllImport
(
"kernel32.dll"
)]
public
static
extern
void
GetSystemTime
(
ref
SYSTEMTIME
lpSystemTime
);
// Структура для GetSystemTime (должна точно соответствовать нативной)
[
StructLayout
(
LayoutKind
.
Sequential
)]
public
struct
SYSTEMTIME
{
public
ushort
wYear
;
public
ushort
wMonth
;
public
ushort
wDayOfWeek
;
public
ushort
wDay
;
public
ushort
wHour
;
public
ushort
wMinute
;
public
ushort
wSecond
;
public
ushort
wMilliseconds
;
}
static
void
Main
(
string
[]
args
)
{
// Вызов нативной функции
OutputDebugString
(
"Hello from P/Invoke! This message goes to debug output.\n"
);
Console
.
WriteLine
(
"OutputDebugString called. Check your debugger's output window."
);
// Получение системного времени
SYSTEMTIME
st
=
new
();
GetSystemTime
(
ref
st
);
Console
.
WriteLine
(
$"Current System Time (UTC):
{
st
.
wHour
:
D2
}
:
{
st
.
wMinute
:
D2
}
:
{
st
.
wSecond
:
D2
}
"
);
Console
.
ReadKey
();
// Ждем, чтобы увидеть результат перед выходом
}
}
}
NativeInvokeWithCallback/NativeInvokeWithCallback.csproj
0 → 100644
Просмотр файла @
a5c6b847
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType>
</PropertyGroup>
</Project>
Пред
1
2
След
Редактирование
Предварительный просмотр
Поддерживает Markdown
0%
Попробовать снова
или
прикрепить новый файл
.
Отмена
You are about to add
0
people
to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Отмена
Пожалуйста,
зарегистрируйтесь
или
войдите
чтобы прокомментировать