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

Kısıtlamalar, bir tür bağımsız değişkeninin sahip olması gereken özellikler hakkında derleyiciyi 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 üyelerini System.Objectvarsayabilir. 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:

Kısıtlama Açıklama
where T : struct Tür bağımsız değişkeni null değer atanamayan bir değer türü olmalıdır. Boş değer atanabilir 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, struct kısıtlama 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 boş değer 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 atanamayan bir tür olmalıdır. Bağımsız değişken, boş değer atanamayan bir başvuru türü veya boş değer 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ış bir tür parametresi belirtmeniz gerektiğinde belirsizliği giderir. Kısıtlama, default veya struct kısıtlaması olmadan class temel yöntemi ifade eder. 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ıtlamaunmanaged, 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şturucuya sahip 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 bağlamda, T belirtilen temel sınıftan türetilen 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üretilen 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 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 bir başvuru türü, boş değer 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. Null atanabilir bir bağlamda, boş değer atanamayan bir başvuru türüyse U , T boş değer atanamayan başvuru türü olmalıdır. Boş değer atanabilir bir başvuru türüyse U , T boş değer atanabilir veya boş değer atanamaz olabilir.

Kısıtlamaları neden kullanmalısınız?

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üretilen 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ıfında çağrılmasına izin verebilir. Aşağıdaki kod örneği, temel sınıf kısıtlaması uygulayarak sınıfına GenericList<T> ekleyebileceğiniz işlevselliği gösterir ( Genel Türlere Giriş bölümünde).

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 öğesinden Employeedevralan bir Employee nesne veya nesne olması garanti edilir.

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ği için test eder. Bu davranış, bu işleçler bağımsız değişken olarak kullanılan bir türe aşırı yüklenmiş olsa bile oluşur. Aşağıdaki kodda bu nokta gösterilmektedir; sınıfı işlecini 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 sınamanız gerekiyorsa önerilen yol, veya where T : IComparable<T> kısıtlamasını where T : IEquatable<T> uygulamak ve arabirimini genel sınıfı oluşturmak için kullanılacak herhangi bir sınıfta uygulamaktır.

Birden çok parametreyi kısıtlama

Aşağıdaki örnekte gösterildiği gibi, birden çok parametreye kısıtlamalar 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 kısıtlaması olmayan tür parametrelerine ilişkisiz tür parametreleri adı verilir. İ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 herhangi bir arabirim türüne açıkça dönüştürülebilir.
  • Bunları null ile karşılaştırabilirsiniz. İlişkisiz bir parametre ile nullkarşılaştırılırsa, 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

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 kısıtlaması gerektiğinde, kısıtlama olarak genel tür parametresinin kullanılması yararlıdır:

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

Önceki örnekte, T yöntemi bağlamında Add bir tür kısıtlaması ve sınıfı bağlamında bir ilişkisiz 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ç 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 sınırlamalar olarak kullanışlılığı sınırlıdır çünkü derleyici tür parametresi hakkında hiçbir şey varsayabileceğinden, parametresinden System.Objecttüretilir. İki tür parametresi arasında devralma ilişkisini zorlamak istediğiniz senaryolarda tür parametrelerini genel sınıflarda kısıtlamalar olarak kullanın.

notnull kısıtlaması

Tür bağımsız değişkeninin notnull null atanamaz bir değer türü veya boş değer atanamayan başvuru türü olması gerektiğini belirtmek için kısıtlamayı kullanabilirsiniz. Diğer kısıtlamaların çoğundan farklı olarak, bir 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 bağlamda kullanıldığında bir etkisi vardır. Kısıtlamayı notnull null 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ıtlaması

Null class atanabilir bağlamdaki kısıtlama, tür bağımsız değişkeninin boş değer atanamayan bir başvuru türü olması gerektiğini belirtir. Boş değer atanabilir bağlamda, bir 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ıtlaması

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 birinin mevcut olması gerekir. class Kısıtlama kullanıldığında, T? için Tnull atanabilir başvuru türüne başvuruldu. C# 9 ile başlayarak, 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>ise T ile T? 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 temel yönteme uygulanacak bir geçersiz kılma 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 tür olarak bilinen bir unsafe tür üzerinde işlecini kullandığından sizeof , önceki yöntemin bir bağlamda derlenmesi gerekir. unmanaged Kısıtlama sizeof olmadan işleç kullanılamaz.

Kısıtlama unmanaged , kısıtlamayı struct gösterir ve bununla birleştirilemiyor. struct Kısıtlama kısıtlamayı ima ettiğinden new()unmanaged, kısıtlamayla new() da 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 bu kısıtlamaya izin vermemiş. kısıtlaması 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öntemini 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. test Hem hem 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 bu kısıtlamaya 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ğrısı EnumNamedValues yapabilirsiniz.

Bir sabit listesi oluşturmak ve değerleriyle adlarından oluşan bir sözlük 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ı senaryolar, bir tür parametresi için sağlanan bir bağımsız değişkenin bu arabirimi uygulamasını gerektirir. Örnek:

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çeren türü 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 tür parametreleri açısından işleçleri tanımlamasını sağlar. Arabirimi uygulayan türler, arabirim yöntemlerini örtük olarak uygulayabilir.

Ayrıca bkz.