التحويل (casting) وتحويل النوع طريقتان مختلفتان لتغيير تعبير من نوع بيانات إلى نوع آخر.
تصنّف وثائق C# تحويل النوع على أنه استخدام عامل as أو عامل is. أما التحويل فيُعرَّف على أنه استخدام عامل التحويل.
في C#، وكثيرًا ما يحدث ذلك خارج نطاق القيم العددية والتسلسلات الهرمية للأصناف، ستضطر إلى إجراء تحويل عبر استدعاء أحد أعضاء النوع "الهدف" مثل Int32.Parse() الذي يحوّل سلسلة نصية إلى عدد صحيح، أو عبر استدعاء عضو من النوع "المصدر" مثل object.ToString(). وينبغي لمطوري JavaScript واللغات الديناميكية الأخرى أن ينتبهوا إلى ذلك.
لاحظ أن عوامل التحويل الضمني والصريح (التي نناقشها في operator-overloading) متاحة، ويمكنها أن تتيح تحويلات شبه حرة إلى أنواعك الخاصة.
نظام الأنواع في C# أكثر صرامة إلى حد ما من نظام C أو JavaScript، ونتيجة لذلك تكون عمليات التحويل أكثر تقييدًا. يحدث التحويل الضمني بين نوعين عدديين ما دام النوع "الهدف" قادرًا على الحفاظ على مدى قيمة النوع "المصدر" وإشارتها. لاحظ في الوثائق الاستثناء المتعلق بالتحويل إلى الأعداد الحقيقية حيث قد تُفقد الدقة.
لا تدلّ أي صيغة خاصة على التحويل الضمني. على سبيل المثال:
int myInt = 1729;
long myLong = myInt;
لا يوجد تحويل ضمني لتعبير عددي (أو تعبير سلسلة نصية) إلى bool. وتوفّر مكتبة الأصناف الأساسية (BCL) الدالة Convert.ToBoolean() لهذا الغرض.
يمكن تحويل تعبير من النوع char ضمنيًا إلى int. أما التحويل في الاتجاه المعاكس فيجب أن يكون صريحًا. وليست كل قيم int محارف utf-16 صالحة.
حيث لا يمكن تحويل الأنواع العددية ضمنيًا، يمكنك عمومًا استخدام عامل التحويل الصريح.
وحين لا يستطيع النوع "الهدف" تمثيل القيمة الجاري تحويلها لأنه ليس واسعًا بما يكفي أو لوجود تعارض في الإشارة، فقد يُطرح استثناء تجاوز السعة في حالة الأعداد الصحيحة، أو قد يأخذ متغير النوع "الهدف" القيمة Infinity في حالة الأعداد float وdouble.
يمكن تحويل تعبير من النوع int صراحةً إلى char. وقد ينتج عن ذلك محرف char غير صالح.
int largeInt = Int32.MaxValue;
int largeNegInt = Int32.MinValue;
ushort shortUnsignedInt = ushort.MaxValue;
float largeFloat = float.MaxValue;
float smallFloat = 17.29f;
// implicit cast
int from_ushort = shortUnsignedInt; // 65535
float from_int = largeInt; // -21474836E+09
int from_char = 'a'; // 96
// explicit cast
uint from_largeInt = (uint)largeInt; // 2147483647
uint from_neg = (uint) largeNegInt; // 2147483648 or OverflowException is thrown (if checked)
int from_smallFloat = (int) smallFloat; // 17
int from_largeFloat = (int) largeFloat; // -2147483648 or OverflowException is thrown (if checked)
char from_intc = (char) 32; // ' '
char from_invalid_int = (char) 0xdcbf; // invalid char - no exception thrown
// no cast available
int fromString = Int32.Parse("42"); // 42
string toString = largeInt.ToString(); // "2147483647"
int fromString_bad = Int32.Parse("forty two"); // FormatException is thrown
راجع هذا المقال للاطلاع على الكلمة المفتاحية checked.
يمكن تحويل أي نوع ضمنيًا إلى صنفه الأساسي أو واجهته.
IList<int> ll = new List<int>();
ويمكن استخدام عامل تحويل للتحويل من صنف أساسي إلى صنف مشتق.
IList<int> ll = new List<int>();
List<int> l = (List<int>)ll;
إذا فشل التحويل لأن نوع التعبير الجاري تحويله غير مرتبط بالنوع المحوَّل إليه، فسيُطرح استثناء من نوع InvalidCastException.
والنهج الأكثر شيوعًا في تحويلات النوع هذه هو استخدام الكلمة المفتاحية is:
interface IFoo { int Bar(); }
class Foo : IFoo { public int Bar() { return 42; }}
Object r = new Random();
IFoo ifoo = new Foo();
Object o = new Foo();
int result = 1729;
if (ifoo is Foo foo)
{
result = foo.Bar();
}
// result == 42
result = 1729;
if (o is Foo foo2)
{
result = foo2.Bar();
}
// result == 42
result = 1729;
if (r is Foo foo3)
{
result = foo3.Bar();
}
// result == 1729
وتؤدي الكلمة المفتاحية as وظيفة مشابهة لدور is، مثل var foo = ifoo as Foo;. في هذا المثال سيكون foo مساويًا لـ null إذا لم يكن ifoo من النوع Foo، وإلا فسيُسند ifoo إليه.
يمكن للأنواع أن تعرّف عوامل تحويل صريحة وضمنية مخصصة خاصة بها.
من أمثلة التحويلات الصريحة والضمنية في BCL التحويلات من البنية BigInteger وإليها من وإلى أنواع عددية أخرى.
typeof
إذا احتجت إلى اكتشاف النوع الدقيق لكائن ما، فقد يكون is متسامحًا أكثر من اللازم، لأنه سيحوّل الكائن إلى صنف أو إلى أي من أصنافه الأساسية. وفي هذه الحالة يكون الحل هو typeof وObject.GetType().
object o = new List<int>();
o is ICollection<int> // true
o.GetType() == typeof(ICollection<int>) // false
o is List<int> // true
o.GetType() == typeof(List<int>) // true