By marking a type, type member, or statement block with the unsafe keyword, you're
permitted to use pointer types and perform C++ style pointer operations on memory
within that scope, and to be able to do this within the managed execution framework.
Unsafe code can run faster than a corresponding safe implementation.
Here is a nice, short example that comes from the book C# 4.0 in a Nutshell:
unsafe void BlueFilter (int[,] bitmap)
{
int length = bitmap.Length;
fixed (int* b=bitmap)
{
int* p=b;
for (int i=0, i<length; i++)
*p++ &= 0xFF;
}
}
In the above case, the "safe" version would have required a nested loop
with array indexing and bounds checking. Unsafe C# methods may also be faster
than calling an external C function, since there is no overhead associated with
leaving the managed execution environment.
The fixed statement is required to pin a managed object such as the bitmap in the
code snippet above. This is necessary when using pointer operations as we don't
want the garbage collector moving our objects around in memory while we're attempting
to do operations on them.
Just for kicks, lets write some code that gets an image and iterates over each pixel,
getting the color and then setting a color back again - a very common operation.
We'll do it first with "safe" code, and then a second time with unsafe
code in the form of an UnsafeBitmap class (the original version of this comes
from an article by Eric Gunnerson of Microsoft).
Here is the sample code:
using System.Text;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
namespace ImageManipulation
{
class Program
{
private static Stopwatch sw;
static void Main(string[] args)
{
string imagePath = System.Environment.CurrentDirectory + @"\logo1w.png";
Bitmap img = (Bitmap)Image.FromFile(imagePath);
var newBmp = new Bitmap(img.Width, img.Height, PixelFormat.Format32bppArgb);
// convert the google logo to non-indexed format
using(var gfx = Graphics.FromImage(newBmp) )
{
gfx.DrawImage(img, 0, 0);
}
sw = new Stopwatch();
sw.Start();
// We'll do the entire operation ten times
for (int a = 0; a < 10; a++)
{
for (int x = 0; x < newBmp.Width; x++)
{
for (int y = 0; y < newBmp.Height; y++)
{
Color c = newBmp.GetPixel(x, y);
newBmp.SetPixel(x, y, c);
}
}
}
sw.Stop();
Console.WriteLine("Bitmap: " + sw.ElapsedMilliseconds);
UnsafeBitmap uBmp = new UnsafeBitmap(newBmp);
sw.Reset();
sw.Start();
for (int b = 0; b < 10; b++)
{
uBmp.LockBitmap();
for (int x = 0; x < newBmp.Width; x++)
{
for (int y = 0; y < newBmp.Height; y++)
{
PixelData pdata = uBmp.GetPixel(x, y);
uBmp.SetPixel(x, y, pdata);
}
}
uBmp.UnlockBitmap();
}
sw.Stop();
Console.WriteLine("Unsafe Bitmap: " + sw.ElapsedMilliseconds);
Console.ReadLine();
}
}
}
And here is the code for the UnsafeBitmap class:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
namespace ImageManipulation
{
public unsafe class UnsafeBitmap
{
Bitmap bitmap;
int width;
BitmapData bitmapData = null;
Byte* pBase = null;
public UnsafeBitmap(Bitmap bitmap)
{
this.bitmap = new Bitmap(bitmap);
}
public UnsafeBitmap(int width, int height)
{
this.bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
}
public void Dispose()
{
bitmap.Dispose();
}
public Bitmap Bitmap
{
get
{
return (bitmap);
}
}
private Point PixelSize
{
get
{
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF bounds = bitmap.GetBounds(ref unit);
return new Point((int)bounds.Width, (int)bounds.Height);
}
}
public void LockBitmap()
{
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF boundsF = bitmap.GetBounds(ref unit);
Rectangle bounds = new Rectangle((int)boundsF.X,
(int)boundsF.Y,
(int)boundsF.Width,
(int)boundsF.Height);
width = (int)boundsF.Width * sizeof(PixelData);
if (width % 4 != 0)
{
width = 4 * (width / 4 + 1);
}
bitmapData =
bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
pBase = (Byte*)bitmapData.Scan0.ToPointer();
}
public PixelData GetPixel(int x, int y)
{
PixelData returnValue = *PixelAt(x, y);
return returnValue;
}
public void SetPixel(int x, int y, PixelData colour)
{
PixelData* pixel = PixelAt(x, y);
*pixel = colour;
}
public void UnlockBitmap()
{
bitmap.UnlockBits(bitmapData);
bitmapData = null;
pBase = null;
}
public PixelData* PixelAt(int x, int y)
{
return (PixelData*)(pBase + y * width + x * sizeof(PixelData));
}
}
public struct PixelData
{
public byte blue;
public byte green;
public byte red;
}
}
Wnen you run the above Console Application, you will see two lines output - the elapsed
milliseconds to get and to then write each pixel via managed "safe"
code (ten times), and the elapsed milliseconds to get and write each pixel using
"unsafe" code with pointer operations (again, the entire image ten
times). There is nothing dangerous or "unsafe" in doing this as you're
constraining all operations to the precise bounds of the bitmap image in memory.
The "unsafe" keyword is misleading as it gives the wrong connotation.
Managers and other types who are concerned are likely to misinterpret the concept
and issue arbitrary rules against the use of "unsafe" anything, regardless of whether they fully understand it or not.
Depending on the speed of your CPU, etc. the unsafe version should be over eight
times faster! The .NET Framework GDI+ versions of GetPixel and SetPixel are annoyingly
slow, and if you need to do a lot of image manipulation, you can certainly avoid
them and get a huge performance boost. Incidentally, the WPF implementation of
the WriteableBitmap class is actually about as slow as GDI+, or perhaps even
slower!
You can download the sample Visual Studio 2010 solution here.