Genel yöntem çoklu (VEYA) türü kısıtlaması


135

Okuma bu , ben bir yöntem genel bir yöntem yaparak birden fazla türde parametrelerini kabul etmesi için mümkündü öğrendik. Örnekte, aşağıdaki kod, "U" öğesinin bir olduğundan emin olmak için bir tür kısıtlamasıyla birlikte kullanılır IEnumerable<T>.

public T DoSomething<U, T>(U arg) where U : IEnumerable<T>
{
    return arg.First();
}

Birden fazla tür kısıtlaması eklemeye izin veren bazı kodlar buldum:

public void test<T>(string a, T arg) where T: ParentClass, ChildClass 
{
    //do something
}

Ancak bu kod zorunlu kılmak amacıyla görünen argbir türü hem olmalı ParentClass ve ChildClass . Ne yapmak istiyorum arg bir tür ParentClass veya ChildClass aşağıdaki şekilde olabilir:

public void test<T>(string a, T arg) where T: string OR Exception
{
//do something
}

Yardımınız her zamanki gibi takdir edilmektedir!


4
Böyle bir yöntemin gövdesinde genel bir şekilde yararlı bir şekilde ne yapabilirdiniz (çoklu türlerin tümü belirli bir temel sınıftan türetilmedikçe, bu durumda neden tür kısıtlaması olarak beyan etmiyorsunuz)?
Damien_The_Unbeliever

@Damien_The_Unbeliever Vücutta ne demek istediğinizden emin değil misiniz? Eğer herhangi bir tür izin ve vücutta elle kontrol demek istemiyorsanız ... ve gerçek kod yazmak istiyorum (son kod snippet'i), ben bir dize VEYA istisna geçmek mümkün olmak istiyorum, bu yüzden sınıf yok orada ilişki (system.object hariç hayal).
Mansfield

1
Ayrıca mühürlü bir sınıfta olduğu where T : stringgibi yazılı olarak stringda bir faydası olmadığını unutmayın . Yapabileceğiniz tek yararlı şey , @ Botz3000 tarafından aşağıdaki cevabında açıklandığı gibi ve aşırı yükleri tanımlamaktır . stringT : Exception
Mattias Buelens

Ancak bir ilişki olmadığında, çağırabileceğiniz tek yöntem arg, tanımlanmış yöntemlerdir object- neden sadece jenerikleri resimden kaldırmak ve türünü yapmak değil arg object? Ne kazanıyorsun?
Damien_The_Unbeliever

1
@Mansfield Bir nesne parametresini kabul eden özel bir yöntem yapabilirsiniz . Her iki aşırı yüklenme de buna aşırı yük diyecektir. Burada jeneriklere gerek yok.
Botz3000

Yanıtlar:


68

Bu imkansız. Bununla birlikte, belirli tipler için aşırı yüklenmeleri tanımlayabilirsiniz:

public void test(string a, string arg);
public void test(string a, Exception arg);

Bunlar genel bir sınıfın parçasıysa, yöntemin genel sürümü yerine tercih edilecektir.


1
İlginç. Bu benim başıma gelmişti, ama bir işlevi kullanmak için kod temiz hale getirebilir düşündüm. Ah, çok teşekkürler! Sadece meraktan, bunun mümkün olmasının belirli bir nedeni olup olmadığını biliyor musunuz? (kasıtlı olarak dilden çıkarıldı mı?)
Mansfield

3
@ Manfield Kesin sebebini bilmiyorum, ama artık genel parametreleri anlamlı bir şekilde kullanamayacağınızı düşünüyorum. Sınıf içinde, tamamen farklı tipte olmalarına izin verildiyse, nesne gibi muamele görmeleri gerekirdi. Bu, genel parametreyi dışarıda bırakabileceğiniz ve aşırı yük sağlayabileceğiniz anlamına gelir.
Botz3000

3
@ Mansfield, bir orilişkinin işleri genel olmak için çok kullanışlı hale getirmesidir. Ne yapacağınızı ve tüm bunları anlamak için yansıma yapmanız gerekir . (Yuck!).
Chris Pfohl

29

Botz cevabı% 100 doğru, kısa bir açıklama:

Bir yöntem yazarken (genel veya değil) ve yöntemin aldığı parametre türlerini bildirirken bir sözleşme tanımlayabilirsiniz:

Bana Tip T'nin nasıl yapılacağını bildiği şeyleri nasıl yapacağını bilen bir nesne verirseniz, 'a' ya da beyan ettiğim tipin dönüş değeri ya da 'b': bu tür.

Bir kerede birden fazla tür (veya bir) kullanarak veya denerseniz veya sözleşmenin bulanıklaştığı birden fazla türden bir değer döndürmeye çalışırsanız:

Bana ip atlamayı bilen veya 15. basamağa pi hesaplamayı bilen bir nesne verirseniz, balığa çıkabilen bir nesneyi veya betonu karıştırabilirim.

Sorun şu ki, yönteme girdiğinizde size bir IJumpRopeveya a verdiklerine dair hiçbir fikriniz yok PiFactory. Ayrıca, devam edip yöntemi (sihirli bir şekilde derlemek için aldığınız varsayılarak) kullandığınızda, a Fisherveya anınız olduğundan gerçekten emin değilsiniz AbstractConcreteMixer. Temel olarak her şeyi daha karmaşık hale getirir.

Sorununuzun çözümü iki olasılıktan biridir:

  1. Olası her dönüşümü, davranışı veya her şeyi tanımlayan birden fazla yöntem tanımlayın. Botz'un cevabı bu. Programlama dünyasında bu, Yöntemin Aşırı Yüklenmesi olarak adlandırılır.

  2. Yöntem için ihtiyacınız olan her şeyi nasıl yapacağınızı bilen bir temel sınıf veya arabirim tanımlayın ve bir yöntemin yalnızca bu türü almasını sağlayın . Bu, bunları uygulama ile nasıl eşleştirmeyi planladığınızı tanımlamak için a stringve Exceptionküçük bir sınıfın tamamlanmasını içerebilir , ancak her şey çok net ve okunması kolaydır. Dört yıl sonra gelip kodunuzu okuyabilir ve neler olduğunu kolayca anlayabilirim.

Hangisini seçtiğiniz, seçim 1 ve 2'nin ne kadar karmaşık olacağına ve ne kadar genişletilebilir olması gerektiğine bağlıdır.

Özel durumunuz için, istisnadan sadece bir mesaj veya başka bir şey çıkardığınızı hayal edeceğim:

public interface IHasMessage
{
    string GetMessage();
}

public void test(string a, IHasMessage arg)
{
    //Use message
}

Şimdi tek ihtiyacınız olan bir stringve Exceptiona'yı bir IHasMessage'a dönüştüren yöntemlerdir . Çok kolay.


Üzgünüm @ Botz3000, sadece isminizi yanlış yazdım.
Chris Pfohl

Veya derleyici türü işlev içinde bir birleşim türü olarak tehdit edebilir ve dönüş değeri girdiği türden olabilir. TypeScript bunu yapar.
Alex

@Alex ama C # bunu yapmaz.
Chris Pfohl

Bu doğru, ama olabilir. Bu cevabı yapamayacağı şekilde okudum, yanlış yorumladım mı?
Alex

1
Mesele şu ki, C # genel parametre kısıtlamaları ve jenerikler kendileri, örneğin C ++ şablonlarına kıyasla oldukça ilkeldir. C # derleyiciye genel türlerde hangi işlemlere izin verildiğini önceden bildirmenizi gerektirir. Bu bilgiyi sağlamanın yolu, bir uygulama arabirimi kısıtlaması eklemektir (burada T: IDisposable). Ancak, türünüzün genel bir yöntem kullanmak için bazı arabirimleri uygulamasını istemeyebilir veya genel kodunuzda ortak arabirimi olmayan bazı türlere izin vermek isteyebilirsiniz. Ör. değer temelli karşılaştırma için Eşittir (v1, v2) öğesini çağırabilmeniz için herhangi bir yapıya veya dizeye izin verin.
Vakhtang

8

ChildClass, ParentClass öğesinden türetilmişse, ParentClass ve ChildClass öğelerini kabul etmek için aşağıdakileri yazabilirsiniz;

public void test<T>(string a, T arg) where T: ParentClass 
{
    //do something
}

Öte yandan, aralarında kalıtım ilişkisi olmayan iki farklı tür kullanmak istiyorsanız, aynı arabirimi uygulayan türleri dikkate almalısınız;

public interface ICommonInterface
{
    string SomeCommonProperty { get; set; }
}

public class AA : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;set;
    }
}

public class BB : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;
        set;
    }
}

o zaman genel fonksiyonunuzu;

public void Test<T>(string a, T arg) where T : ICommonInterface
{
    //do something
}

İyi bir fikir, ancak yukarıdaki bir yoruma göre mühürlü bir sınıf olan dize kullanmak istediğim için bunu yapabileceğimi sanmıyorum ...
Mansfield

bu aslında bir tasarım problemidir. genel bir işlev, kodun yeniden kullanımı ile benzer işlemleri yapmak için kullanılır. her ikisi de yöntem gövdesinde farklı işlemler yapmayı planlıyorsanız, ayırma yöntemleri daha iyi bir yoldur (IMHO).
13'te daryal

Aslında yaptığım şey basit bir hata günlüğü fonksiyonu yazmak. Bu son parametrenin hata hakkında bir bilgi dizesi veya bir istisna olmasını istiyorum, bu durumda e.message + e.stacktrace'ı zaten bir dize olarak kaydederim.
Mansfield

isSuccesful'a sahip yeni bir sınıf yazabilir, mesaj ve özel durumu özellikler olarak kaydedebilirsiniz. sonra doğrulayıcı olup olmadığını kontrol edebilir ve geri kalanını yapabilirsiniz.
12'de daryal

1

Bu soru kadar eski, yukarıdaki açıklamamda hala rastgele upvotes alıyorum. Açıklama hala olduğu gibi mükemmel bir şekilde duruyor, ancak sendika türlerinin yerine iyi bir şekilde sunulan bir türle ikinci kez cevap vereceğim (C # tarafından doğrudan desteklenmeyen soruya güçlü bir şekilde yazılan cevap ).

using System;
using System.Diagnostics;

namespace Union {
    [DebuggerDisplay("{currType}: {ToString()}")]
    public struct Either<TP, TA> {
        enum CurrType {
            Neither = 0,
            Primary,
            Alternate,
        }
        private readonly CurrType currType;
        private readonly TP primary;
        private readonly TA alternate;

        public bool IsNeither => currType == CurrType.Primary;
        public bool IsPrimary => currType == CurrType.Primary;
        public bool IsAlternate => currType == CurrType.Alternate;

        public static implicit operator Either<TP, TA>(TP val) => new Either<TP, TA>(val);

        public static implicit operator Either<TP, TA>(TA val) => new Either<TP, TA>(val);

        public static implicit operator TP(Either<TP, TA> @this) => @this.Primary;

        public static implicit operator TA(Either<TP, TA> @this) => @this.Alternate;

        public override string ToString() {
            string description = IsNeither ? "" :
                $": {(IsPrimary ? typeof(TP).Name : typeof(TA).Name)}";
            return $"{currType.ToString("")}{description}";
        }

        public Either(TP val) {
            currType = CurrType.Primary;
            primary = val;
            alternate = default(TA);
        }

        public Either(TA val) {
            currType = CurrType.Alternate;
            alternate = val;
            primary = default(TP);
        }

        public TP Primary {
            get {
                Validate(CurrType.Primary);
                return primary;
            }
        }

        public TA Alternate {
            get {
                Validate(CurrType.Alternate);
                return alternate;
            }
        }

        private void Validate(CurrType desiredType) {
            if (desiredType != currType) {
                throw new InvalidOperationException($"Attempting to get {desiredType} when {currType} is set");
            }
        }
    }
}

Yukarıdaki sınıf olabilir bir tür temsil eder , ya TP veya TA. Bu şekilde kullanabilirsiniz (türler orijinal cevabıma geri döner):

// ...
public static Either<FishingBot, ConcreteMixer> DemoFunc(Either<JumpRope, PiCalculator> arg) {
  if (arg.IsPrimary) {
    return new FishingBot(arg.Primary);
  }
  return new ConcreteMixer(arg.Secondary);
}

// elsewhere:

var fishBotOrConcreteMixer = DemoFunc(new JumpRope());
var fishBotOrConcreteMixer = DemoFunc(new PiCalculator());

Önemli notlar:

  • Kontrol etmezseniz çalışma zamanı hataları alırsınız IsPrimary .
  • Herhangi birini IsNeither IsPrimaryveyaIsAlternate .
  • Değere Primaryve aracılığıyla erişebilirsinizAlternate
  • TP / TA ile Ya değerleri arasında ya değerleri ya Eitherda beklenen herhangi bir yeri iletmenizi sağlayan örtük dönüştürücüler vardır . Eğer varsa yapmak bir pas Eitherbir yerde TAya da TPbekleniyor, ancak Eitherdeğerin yanlış türü içeren bir çalışma zamanı hatası alırsınız.

Genellikle bir sonuç veya bir hata döndürmek için bir yöntem istiyorum bunu kullanın. Bu stil kodunu gerçekten temizler. Ayrıca çok nadiren ( nadiren ) bunu yöntem aşırı yüklemelerinin yerine kullanıyorum. Gerçekçi olarak, bu tür bir aşırı yükün çok zayıf bir yerine geçer.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.