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

Kısıtlamalar, derleyiciyi tür bağımsız değişkeninin sahip olması gereken özellikler hakkında bilgilendirir. Kısıtlama olmaksızın tür argümanı herhangi bir tür olabilir. Derleyici yalnızca herhangi bir .NET türü için nihai temel sınıf olan System.Object üyelerini kabul edebilir. 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 de dahil olmak üzere null olmayan record struct 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ürlerinin, bildirilen veya örtük bir parametresiz oluşturucuya sahip olması nedeniyle, struct kısıtlaması new() kısıtlamasını ifade eder ve new() kısıtlamasıyla birleştirilemez. struct kısıtlamasını unmanaged kısıtlamasıyla 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 olmayan bir referans türü olmalıdır.
where T : class? Tür bağımsız değişkeni, null atanabilir veya atanamaz bir referans 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 değer almaz bir tür olmalıdır. Bağımsız değişken, boş değer atanamayan bir referans türü veya boş değer atanamayan bir değer türü olabilir.
where T : unmanaged Tür bağımsız değişkeni null değer alamayan yönetilmeyen bir tür olmalıdır. Kısıtlama unmanaged, struct kısıtlamasını ifade eder ve struct veya new() kısıtlamaları ile birleştirilemez.
where T : new() Tip bağımsız değişkeninin parametresiz genel oluşturucusu olması gerekir. Diğer kısıtlamalarla birlikte kullanıldığında kısıtlamanın new() en son belirtilmesi gerekir. Kısıtlama new() kısıtlamaları struct ve unmanaged ile birleştirilemiyor.
where T : <temel sınıf adı> Tür bağımsız değişkeni belirtilen temel sınıf olmalı veya ondan 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ıf olmalı veya ondan 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. Null değer atanabilir bir bağlamda, T null atanabilir referans tipi, null atanamayan referans tipi veya değer tipi olabilir. T null atanabilir değer türü olamaz.
where T : U T için sağlanan tür bağımsız değişkeni, U için sağlanan bağımsız değişkenden türemeli veya onun türünden olmalıdır. Boş değer atanabilir bir bağlamda, U boş değer atanamayan bir referans türüyse, T de boş değer atanamayan bir referans türü olmalıdır. Eğer U null atanabilir bir başvuru türüyse, T hem null atanabilir hem de 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. Temel yöntem, default kısıtı class veya struct kısıtlamalarının her ikisi olmadan ifade eder. Daha fazla bilgi için C# dil belirtiminin kısıtlama bölümüne bakındefault.
where T : allows ref struct Bu karşı kısıtlama, T için tür bağımsız değişkeninin bir ref struct türü olabileceğini belirtir. Genel tür veya yöntem, herhangi bir örneği T için başvuru güvenlik kurallarına uymalıdır çünkü bu bir ref structolabilir.

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

  • En fazla birine uygulanabilir: struct, class, class?, notnull, ve unmanaged kısıtlamaları. 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?), struct, class, class?, notnull veya unmanaged kısıtlamalarıyla birleştirilemez.
  • 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 Base? kullanın.
  • Bir arabirimin hem null atanamaz hem de null atanabilir biçimini kısıtlama olarak adlandıramazsınız.
  • new() kısıtlaması, struct veya unmanaged kısıtlamalarıyla birleştirilemez. Kısıtlamayı new() belirtirseniz, bu tür parametresi için son kısıtlama olmalıdır. Eğer uygulanabilirse, anti-kısıtlamalar new() kısıtlamasını takip edebilir.
  • Kısıtlama default yalnızca geçersiz kılma veya açık arabirim uygulamalarına uygulanabilir. struct veya class kısıtlamalarının herhangi biriyle birleştirilemez.
  • allows ref struct kısıtlama önleme, class ya da class? kısıtlamasıyla birleştirilemez.
  • Anti-kısıtlama, allows ref struct bu tür parametresi için tüm kısıtlamalara uymalıdır.

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ürün veya bu türden türetilmiş nesnelerin bağımsız değişkenin yerini alabileceğ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, GenericList<T>'teki sınıfına temel sınıf kısıtlaması uygulayarak ekleyebileceğiniz işlevselliği gösterir.

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(t) { Next = head };
        head = n;
    }

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

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

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

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

Bu kısıtlama, genel sınıfın Employee.Name özelliğini kullanabilmesini sağlar. Kısıt, T türündeki tüm nesnelerin Employee nesnesi veya Employee öğesinden devralan bir nesne olacağını belirtir.

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 : notnull, Employee, IComparable<T>, new()
{
    public void AddDefault()
    {
        T 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 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ı açıklar; String sınıfı == işleci aşırı yüklese bile çıkış false olacaktır.

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

private static void TestStringEquality()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new("target");
    string s2 = sb.ToString();
    OpEqualsTest(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, where T : IEquatable<T> veya where T : IComparable<T> kısıtlamasını uygulayın ve genel sınıfı oluşturmak için kullanılan herhangi bir sınıfta arabirimi 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.
  • System.Object öğesine veya öğesinden veya açıkça herhangi bir arabirim türüne dönüştürülebilir.
  • Bunları null ile karşılaştırabilirsiniz. Sınırsız bir parametre null ile karşılaştırıldığında, eğer tip argümanı bir değer türüyse, 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ıdır ve Add sınıfı bağlamında sınırsız bir tür parametresidir.

Tür parametreleri, genel sınıf tanımlarında kısıtlamalar olarak da kullanılabilir. Tür parametresi, diğer tür parametrelerle birlikte açılı parantezler 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ımının yararı sınırlıdır çünkü derleyici, parametrenin System.Object'dan türediği dışında tür parametresi hakkında başka hiçbir varsayımda bulunamaz. 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 kısıtlamasının, null atanamaz bir değer türü veya null atanamaz bir başvuru türü olmasını belirtmek için bu kısıtlamayı kullanabilirsiniz. Diğer çoğu kısıtlamadan farklı olarak, bir tür bağımsız değişkeni notnull kısıtlamasını ihlal ederse, derleyici hata yerine bir uyarı üretir.

notnull kısıtlaması, yalnızca null atanabilir bir bağlamda kullanıldığında etkili olur. 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

Bağlamın null atanabilir olduğu durumda class 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 izni olan referans türlerinin eklenmesi, T?'nin genel bir tür veya yöntem içinde kullanımını karmaşıklaştırır. T?veya struct kısıtlamasıyla class kullanılabilir, ancak bunlardan biri mevcut olmalıdır. class kısıtlaması kullanıldığında, T?, T için null atanabilir bir başvuru türüne işaret eder. T? hiçbir kısıtlama uygulanmadığında kullanılabilir. Bu durumda, T? değer türleri ve başvuru türleri için T? olarak yorumlanır. Ancak, T bir Nullable<T> örneği ise, T? ile T aynıdır. Başka bir deyişle, haline gelmez T??.

Artık T? veya class kısıtlaması olmadan struct 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 ne class ne de struct kısıtlamasını uyguladığında, türetilmiş sınıfların, herhangi bir kısıtlama olmadan temel yönteme bir geçersiz kılmanın uygulanacağını bir şekilde belirtmesi gerekir. Türetilmiş yöntem kısıtlamayı default uygular. default kısıtlaması ne ne de class kısıtlamasını açıklar.

Yönetilmeyen kısıtlama

Belirli bir tür parametresinin null atanamaz bir unmanaged 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;
}

Bilinmeyen bir tür olan unsafe üzerinde sizeof işleci kullanıldığından ve bu türün yerleşik bir tür olmadığı bilindiğinden, önceki yöntemin bir bağlamda derlenmesi zorunludur. unmanaged Kısıtlama olmadan işleç sizeof kullanılamaz.

unmanaged kısıtlaması struct kısıtlamasını ima eder ve bununla birleştirilemez. struct kısıtlaması, new() kısıtlamasını ima ettiğinden, unmanaged kısıtlaması da new() kısıtlamasıyla birleştirilemez.

Temsilci kısıtlamaları

veya System.Delegate öğesini temel sınıf kısıtlaması olarak kullanabilirsinizSystem.MulticastDelegate. Ortak Dil Çalışma Zamanı (CLR) bu kısıtlamaya her zaman izin verdi, ancak C# dili buna 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:

extension<TDelegate>(TDelegate source) where TDelegate : System.Delegate
{
    public TDelegate? TypeSafeCombine(TDelegate target)
        => 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, derlenmez. Hem hem first de test temsilci türleridir, ancak bunlar farklı temsilci türleridir.

Enum 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ş. System.Enumk kullanan genellikler, System.Enum'deki statik yöntemleri kullanarak 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.

extension<T>(T) where T : System.Enum
{
    public static Dictionary<int, string> EnumNamedValues()
    {
        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 üzerinde etkileri olan yansımayı kullanır. 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 .

Enum oluşturmak ve değerleri ile adlarının bir sözlüğünü 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>
{
    static abstract T operator +(T left, T right);
    static abstract 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>
{
    static abstract IAdditionSubtraction<T> operator +(
        IAdditionSubtraction<T> left,
        IAdditionSubtraction<T> right);

    static abstract 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.

ref struct'a izin verir

Kısıtlama önleme, allows ref struct karşılık gelen tür bağımsız değişkeninin bir ref struct tür olabileceğini bildirir. Bu tür parametrenin örnekleri aşağıdaki kurallara uymalıdır:

  • Kutulandırılamaz.
  • Başvuru güvenliği kurallarına katılır.
  • ref struct türünün izin verilmediği alanlar gibi yerlerde örnekler kullanılamaz.
  • Örnekler scoped modifikatör ile işaretlenebilir.

allows ref struct maddesi devralınmıyor. Aşağıdaki kodda:

class SomeClass<T, S>
    where T : allows ref struct
    where S : T
{
    // etc
}

Bağımsız değişken S için, ref structS yan tümcesine sahip olmadığından bir allows ref struct olamaz.

allows ref struct kısıtına sahip bir tür parametresi, ilgili tür parametresi de allows ref struct kısıtına sahip değilse, tür bağımsız değişkeni olarak kullanılamaz. Bu kural aşağıdaki örnekte gösterilmiştir:

public class Allow<T> where T : allows ref struct
{

}

public class Disallow<T>
{
}

public class Example<T> where T : allows ref struct
{
    private Allow<T> fieldOne; // Allowed. T is allowed to be a ref struct

    private Disallow<T> fieldTwo; // Error. T is not allowed to be a ref struct
}

Yukarıdaki örnek, ref struct türü olabilecek bir tür bağımsız değişkeninin, ref struct türü olamayacak bir tür parametresiyle değiştirilemeyeceğini göstermektedir.

Ayrıca bkz.