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:

Kısıtlama Açıklama
where T : struct Tür bağımsız değişkeni, türleri içeren null atanamaz bir değer türü olmalıdır.record struct Boş değer türleri hakkında bilgi için bkz . Null atanabilir değer türleri. Tüm değer türlerinin, bildirilen veya örtük olarak 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 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, kayıtlar da dahil olmak üzere tüm sınıf, arabirim, temsilci veya dizi türleri 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 : 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, T null atanabilir bir başvuru türü, null atanamayan başvuru türü veya değer türü olabilir. T null atanabilir bir değer türü olamaz.
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 boş değer atanamayan bir 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.
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.

Bazı kısıtlamalar birbirini dışlar ve bazı kısıtlamalar belirtilen sırada olmalıdır:

  • , , class, class?notnullve unmanaged kısıtlamalarının structen çoğunu uygulayabilirsiniz. Bu kısıtlamalardan herhangi birini sağlarsanız, bu tür parametresi için belirtilen ilk kısıtlama olmalıdır.
  • Temel sınıf kısıtlaması (where T : Base veya where T : Base?), , , classclass?veya notnullunmanagedkısıtlamalarıyla structbirleştirilemiyor.
  • Her iki biçimde de en fazla bir temel sınıf kısıtlaması uygulayabilirsiniz. Boş değer atanabilir taban türünü desteklemek istiyorsanız kullanın Base?.
  • Bir arabirimin hem null atanamaz hem de null atanabilir biçimini kısıtlama olarak adlandıramazsınız.
  • Kısıtlama new() veya unmanaged kısıtlamasıyla struct birleştirilemiyor. Kısıtlamayı new() belirtirseniz, bu tür parametresi için son kısıtlama olmalıdır.
  • Kısıtlama default yalnızca geçersiz kılma veya açık arabirim uygulamalarına uygulanabilir. Veya kısıtlamalarıyla structclass birleştirilemiyor.

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 tarafından desteklenmeyen System.Objectyöntemleri çağırmayı içeren basit atamanın ötesinde genel üyeler üzerinde herhangi bir işlem kullandığında 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 bu tür bağımsız değişkeninin yerini aabileceğini 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, System.Collections.Generic.IList<T>, IDisposable, 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 sınar. 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, veya where T : IComparable<T> kısıtlamasını where T : IEquatable<T> uygulayın ve genel sınıfı oluşturmak için kullanılan herhangi bir sınıfta arabirimini uygulayın.

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 desteklediğinin garantisi olmadığından 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ü olduğunda 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üretilmiş yöntem kısıtlamayı default uygular. 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.