Tür parametrelerindeki kısıtlamalar (C# Programlama Kılavuzu)

Kısıtlamalar, derleyiciyi bir tür bağımsız değişkeninin sahip olması gereken özellikler hakkında bilgilendirmektedir. Herhangi bir kısıtlama olmadan tür bağımsız değişkeni herhangi bir tür olabilir. Derleyici yalnızca herhangi bir .NET türü için nihai temel sınıf olan öğesinin System.Objectüyelerini varsayabilir. Daha fazla bilgi için bkz . Kısıtlamalar neden kullanılır? İstemci kodu bir kısıtlamayı karşılamayan bir tür kullanıyorsa, derleyici bir hata döndürür. Kısıtlamalar bağlamsal anahtar sözcük kullanılarak where belirtilir. Aşağıdaki tabloda çeşitli kısıtlama türleri listelenmiştir:

Sınırlama Tanım
where T : struct Tür bağımsız değişkeni null atanamaz bir değer türü olmalıdır. Boş değer türleri hakkında bilgi için bkz . Null atanabilir değer türleri. Tüm değer türleri erişilebilir bir parametresiz oluşturucuya sahip olduğundan kısıtlama struct kısıtlamayı new() gösterir ve kısıtlamayla new() birleştirilemez. Kısıtlamayı struct kısıtlamayla unmanaged birleştiremezsiniz.
where T : class Tür bağımsız değişkeni bir başvuru türü olmalıdır. Bu kısıtlama herhangi bir sınıf, arabirim, temsilci veya dizi türü için de geçerlidir. Boş değer atanabilir bir bağlamda, T null atanamayan bir başvuru türü olmalıdır.
where T : class? Tür bağımsız değişkeni, null atanabilir veya null atanamaz bir başvuru türü olmalıdır. Bu kısıtlama herhangi bir sınıf, arabirim, temsilci veya dizi türü için de geçerlidir.
where T : notnull Tür bağımsız değişkeni null atanamaz bir tür olmalıdır. Bağımsız değişken, boş değer atanamayan bir başvuru türü veya null atanamayan bir değer türü olabilir.
where T : default Bu kısıtlama, bir yöntemi geçersiz kılarken veya açık bir arabirim uygulaması sağlarken kısıtlanmamış tür parametresi belirtmeniz gerektiğinde belirsizliği giderir. kısıtlamasıdefault, veya struct kısıtlaması olmadan class temel yöntemi gösterir. Daha fazla bilgi için kısıtlama belirtimi teklifine default bakın.
where T : unmanaged Tür bağımsız değişkeni null atanamaz yönetilmeyen bir tür olmalıdır. Kısıtlama unmanaged kısıtlamayı struct gösterir ve veya new() kısıtlamalarıyla struct birleştirilemiyor.
where T : new() Tür bağımsız değişkeninin genel parametresiz oluşturucusunun olması gerekir. Diğer kısıtlamalarla birlikte kullanıldığında kısıtlamanın new() en son belirtilmesi gerekir. Kısıtlama new() ve unmanaged kısıtlamalarıyla struct birleştirilemiyor.
where T :<temel sınıf adı> Tür bağımsız değişkeni belirtilen temel sınıftan türemelidir veya türemelidir. Null atanabilir bir bağlamda, T belirtilen temel sınıftan türetilmiş null atanamaz bir başvuru türü olmalıdır.
where T :<temel sınıf adı>? Tür bağımsız değişkeni belirtilen temel sınıftan türemelidir veya türemelidir. Null atanabilir bir bağlamda, T belirtilen temel sınıftan türetilmiş null atanabilir veya null atanamaz bir tür olabilir.
where T :<arabirim adı> Tür bağımsız değişkeninin belirtilen arabirim olması veya uygulaması gerekir. Birden çok arabirim kısıtlaması belirtilebilir. Kısıtlayan arabirim de genel olabilir. Null atanabilir bir bağlamda, T belirtilen arabirimi uygulayan null atanamaz bir tür olmalıdır.
where T :<arabirim adı>? Tür bağımsız değişkeninin belirtilen arabirim olması veya uygulaması gerekir. Birden çok arabirim kısıtlaması belirtilebilir. Kısıtlayan arabirim de genel olabilir. Boş değer atanabilir bir bağlamda null T atanabilir başvuru türü, null atanamayan başvuru türü veya değer türü olabilir. T null atanabilir bir değer türü olmayabilir.
where T : U için sağlanan tür bağımsız değişkeni, için TUsağlanan bağımsız değişkenden türetilmelidir veya türünden türemelidir. Boş değer atanabilir bir bağlamda, boş değer atanamayan bir başvuru türüyse U , T null atanamaz başvuru türü olmalıdır. Boş değer atanabilir bir başvuru türüyse U , T null atanabilir veya null atanamaz olabilir.

Kısıtlamalar neden kullanılır?

Kısıtlamalar, bir tür parametresinin özelliklerini ve beklentilerini belirtir. Bu kısıtlamaları bildirmek, kısıtlayan türün işlemlerini ve yöntem çağrılarını kullanabileceğiniz anlamına gelir. Genel sınıfınız veya yönteminiz basit atamanın ötesinde genel üyeler üzerinde herhangi bir işlem kullanıyorsa veya tarafından System.Objectdesteklenmeyen yöntemleri çağırıyorsa, tür parametresine kısıtlamalar uygularsınız. Örneğin, temel sınıf kısıtlaması derleyiciye yalnızca bu türdeki veya bu türden türetilmiş nesnelerin tür bağımsız değişkenleri olarak kullanılacağını söyler. Derleyici bu garantiye sahip olduktan sonra, bu türdeki yöntemlerin genel sınıfta çağrılmasına izin verebilir. Aşağıdaki kod örneği, temel sınıf kısıtlaması uygulayarak sınıfa GenericList<T> ekleyebileceğiniz işlevselliği gösterir (Genel Giriş'te).

public class Employee
{
    public Employee(string name, int id) => (Name, ID) = (name, id);
    public string Name { get; set; }
    public int ID { get; set; }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        public Node(T t) => (Next, Data) = (null, t);

        public Node? Next { get; set; }
        public T Data { get; set; }
    }

    private Node? head;

    public void AddHead(T t)
    {
        Node n = new Node(t) { Next = head };
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node? current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T? FindFirstOccurrence(string s)
    {
        Node? current = head;
        T? t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property.
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}

kısıtlaması, genel sınıfın özelliğini kullanmasını Employee.Name sağlar. kısıtlaması, türündeki T tüm öğelerin bir Employee nesne veya öğesinden Employeedevralan bir nesne olmasını garanti eder.

Aynı tür parametresine birden çok kısıtlama uygulanabilir ve kısıtlamalar aşağıdaki gibi genel türler olabilir:

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}

Kısıtlamayı where T : class uygularken tür parametresinde == ve != işleçlerinden kaçının çünkü bu işleçler değer eşitliği için değil yalnızca başvuru kimliğini test eder. Bu işleçler bağımsız değişken olarak kullanılan bir türde aşırı yüklenmiş olsa bile bu davranış oluşur. Aşağıdaki kod bu noktayı gösterir; sınıfı işleci aşırı yüklese String bile çıkış false değeridir == .

public static void OpEqualsTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}

private static void TestStringEquality()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpEqualsTest<string>(s1, s2);
}

Derleyici yalnızca derleme zamanında bir başvuru türü olduğunu T bilir ve tüm başvuru türleri için geçerli olan varsayılan işleçleri kullanmalıdır. Değer eşitliğini test etmeniz gerekiyorsa, önerilen yol veya where T : IComparable<T> kısıtlamasını where T : IEquatable<T> uygulamak ve genel sınıfı oluşturmak için kullanılacak herhangi bir sınıfta arabirimi uygulamaktır.

Birden çok parametreyi kısıtlama

Aşağıdaki örnekte gösterildiği gibi birden çok parametreye ve tek bir parametreye birden çok kısıtlama uygulayabilirsiniz:

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new()
{ }

İlişkisiz tür parametreleri

Genel sınıftaki SampleClass<T>{}T gibi hiçbir kısıtlaması olmayan tür parametreleri, ilişkisiz tür parametreleri olarak adlandırılır. İlişkisiz tür parametreleri aşağıdaki kurallara sahiptir:

  • Somut tür bağımsız değişkeninin != bu işleçleri desteklemesi garanti edilmediğinden ve == işleçleri kullanılamaz.
  • Bunlar herhangi bir arabirim türüne ve türüne System.Object dönüştürülebilir veya açıkça dönüştürülebilir.
  • Bunları null ile karşılaştırabilirsiniz. İlişkisiz bir parametre ile nullkarşılaştırıldığında, tür bağımsız değişkeni bir değer türündeyse karşılaştırma her zaman false döndürür.

Parametreleri kısıtlama olarak yazın

Bir genel tür parametresinin kısıtlama olarak kullanılması, aşağıdaki örnekte gösterildiği gibi kendi tür parametresine sahip bir üye işlevinin bu parametreyi içeren türün tür parametresiyle sınırlaması gerektiğinde kullanışlıdır:

public class List<T>
{
    public void Add<U>(List<U> items) where U : T {/*...*/}
}

Önceki örnekte, T yöntemi bağlamında bir tür kısıtlaması Add ve sınıfı bağlamında ilişkisiz bir tür parametresidir List .

Tür parametreleri, genel sınıf tanımlarında kısıtlamalar olarak da kullanılabilir. Tür parametresi, diğer tür parametreleriyle birlikte açılı ayraçlar içinde bildirilmelidir:

//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }

Tür parametrelerinin genel sınıflarla kısıtlama olarak kullanışlılığı sınırlıdır, çünkü derleyici tür parametresi hakkında hiçbir şey varsayamaz, ancak parametresinden System.Objecttüretilir. tür parametrelerini, iki tür parametresi arasında devralma ilişkisini zorlamak istediğiniz senaryolarda genel sınıflarda kısıtlamalar olarak kullanın.

notnull Kısıtlama

Tür bağımsız değişkeninin notnull null atanamaz bir değer türü veya null atanamaz başvuru türü olması gerektiğini belirtmek için kısıtlamayı kullanabilirsiniz. Diğer kısıtlamaların çoğundan farklı olarak, tür bağımsız değişkeni kısıtlamayı notnull ihlal ederse, derleyici hata yerine bir uyarı oluşturur.

Kısıtlamanın notnull yalnızca null atanabilir bir bağlamda kullanıldığında bir etkisi vardır. Kısıtlamayı notnull boş değer atanabilir bir belirsiz bağlama eklerseniz, derleyici kısıtlama ihlalleri için herhangi bir uyarı veya hata oluşturmaz.

class Kısıtlama

Null class atanabilir bağlamdaki kısıtlama, tür bağımsız değişkeninin null atanamaz bir başvuru türü olması gerektiğini belirtir. Boş değer atanabilir bir bağlamda, tür bağımsız değişkeni null atanabilir bir başvuru türü olduğunda, derleyici bir uyarı oluşturur.

default Kısıtlama

Null atanabilir başvuru türlerinin eklenmesi, genel bir tür veya yöntemde kullanımını T? karmaşıklaştırır. T?veya class kısıtlamasıyla struct kullanılabilir, ancak bunlardan biri mevcut olmalıdır. class Kısıtlama kullanıldığında, T? için Tnull atanabilir başvuru türüne başvurur. T? hiçbir kısıtlama uygulanmadığında kullanılabilir. Bu durumda, T? değer türleri ve başvuru türleri olarak T? yorumlanır. Ancak, örneği Nullable<T>T? ise T ile aynıdırT. Başka bir deyişle, haline gelmez T??.

Artık T? veya struct kısıtlaması olmadan class kullanılabildiğinden geçersiz kılmalarda veya açık arabirim uygulamalarında belirsizlikler ortaya çıkabilir. Her iki durumda da geçersiz kılma kısıtlamaları içermez, ancak bunları temel sınıftan devralır. Temel sınıf veya struct kısıtlamasını class uygulamadığında, türetilmiş sınıfların herhangi bir kısıtlama olmadan bir geçersiz kılmanın temel yönteme uygulanacağını belirtmesi gerekir. Türetilen yöntem kısıtlamayı uyguladığında default . Kısıtlama default ne ne destruct kısıtlamasını net bir şekilde class açıklar.

Yönetilmeyen kısıtlama

tür parametresinin unmanaged null atanamaz yönetilmeyen bir tür olması gerektiğini belirtmek için kısıtlamasını kullanabilirsiniz. Kısıtlama, unmanaged aşağıdaki örnekte gösterildiği gibi bellek blokları olarak işlenebilen türlerle çalışmak için yeniden kullanılabilir yordamlar yazmanızı sağlar:

unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
    var size = sizeof(T);
    var result = new Byte[size];
    Byte* p = (byte*)&argument;
    for (var i = 0; i < size; i++)
        result[i] = *p++;
    return result;
}

Yerleşik bir tür olduğu bilinmeyen bir unsafe tür üzerinde işlecini sizeof kullandığından, önceki yöntemin bir bağlamda derlenmesi gerekir. unmanaged Kısıtlama olmadan işleç sizeof kullanılamaz.

Kısıtlama, unmanaged kısıtlamayı struct gösterir ve bununla birleştirilemiyor. Kısıtlama kısıtlamayı struct ima ettiğinden new()unmanaged , kısıtlama da kısıtlamayla new() birleştirilemiyor.

Temsilci kısıtlamaları

veya System.MulticastDelegate öğesini temel sınıf kısıtlaması olarak kullanabilirsinizSystem.Delegate. CLR bu kısıtlamaya her zaman izin verdi, ancak C# dili izin vermemiş. Kısıtlama, System.Delegate temsilcilerle tür açısından güvenli bir şekilde çalışan kod yazmanızı sağlar. Aşağıdaki kod, aynı türde olmaları koşuluyla iki temsilciyi birleştiren bir uzantı yöntemi tanımlar:

public static TDelegate? TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : System.Delegate
    => Delegate.Combine(source, target) as TDelegate;

Aynı türdeki temsilcileri birleştirmek için yukarıdaki yöntemi kullanabilirsiniz:

Action first = () => Console.WriteLine("this");
Action second = () => Console.WriteLine("that");

var combined = first.TypeSafeCombine(second);
combined!();

Func<bool> test = () => true;
// Combine signature ensures combined delegates must
// have the same type.
//var badCombined = first.TypeSafeCombine(test);

Son satırın açıklamasını kaldırdığınızda, bu satır derlenmez. Hem hem test de first temsilci türleridir, ancak bunlar farklı temsilci türleridir.

Sabit listesi kısıtlamaları

Türü temel sınıf kısıtlaması olarak da belirtebilirsiniz System.Enum . CLR bu kısıtlamaya her zaman izin verdi, ancak C# dili izin vermemiş. kullanan System.Enum genel değerler, içindeki statik yöntemleri System.Enumkullanarak sonuçları önbelleğe almak için tür açısından güvenli programlama sağlar. Aşağıdaki örnek bir sabit listesi türü için tüm geçerli değerleri bulur ve ardından bu değerleri dize gösterimiyle eşleyen bir sözlük oluşturur.

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item)!);
    return result;
}

Enum.GetValues ve Enum.GetName performans üzerindeki etkileri olan yansımayı kullanın. Yansıma gerektiren çağrıları yinelemek yerine önbelleğe alınmış ve yeniden kullanılan bir koleksiyon oluşturmak için çağırabilirsiniz EnumNamedValues .

Bir sabit listesi oluşturmak ve değerlerinin ve adlarının bir sözlüğü oluşturmak için aşağıdaki örnekte gösterildiği gibi kullanabilirsiniz:

enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
var map = EnumNamedValues<Rainbow>();

foreach (var pair in map)
    Console.WriteLine($"{pair.Key}:\t{pair.Value}");

Tür bağımsız değişkenleri bildirilen arabirimi uygular

Bazı senaryolarda, tür parametresi için sağlanan bir bağımsız değişkenin bu arabirimi uygulaması gerekir. Örneğin:

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{
    public abstract static T operator +(T left, T right);
    public abstract static T operator -(T left, T right);
}

Bu düzen, C# derleyicisinin aşırı yüklenmiş işleçler veya herhangi bir static virtual veya static abstract yöntem için içerik türünü belirlemesini sağlar. Ekleme ve çıkarma işleçlerinin içeren bir tür üzerinde tanımlanabilmesi için söz dizimini sağlar. Bu kısıtlama olmadan, parametrelerin ve bağımsız değişkenlerin tür parametresi yerine arabirim olarak bildirilmesi gerekir:

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{
    public abstract static IAdditionSubtraction<T> operator +(
        IAdditionSubtraction<T> left,
        IAdditionSubtraction<T> right);

    public abstract static IAdditionSubtraction<T> operator -(
        IAdditionSubtraction<T> left,
        IAdditionSubtraction<T> right);
}

Yukarıdaki söz dizimi, uygulayıcıların bu yöntemler için açık arabirim uygulaması kullanmasını gerektirir. Ek kısıtlamanın sağlanması, arabirimin işleçleri tür parametreleri açısından tanımlamasını sağlar. Arabirimi uygulayan türler, arabirim yöntemlerini örtük olarak uygulayabilir.

Ayrıca bkz.