C # Jenerikler ve Tür Kontrolü


83

IList<T>Bir parametre olarak kullanan bir yöntemim var . O Tnesnenin türünü kontrol etmem ve ona göre bir şeyler yapmam gerekiyor. TDeğeri kullanmaya çalışıyordum , ancak derleyici buna izin vermiyor. Benim çözümüm şudur:

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        if (clause[0] is int || clause[0] is decimal)
        {
           //do something
        }
        else if (clause[0] is String)
        {
           //do something else
        }
        else if (...) //etc for all the types
        else
        {
           throw new ApplicationException("Invalid type");
        }
    } 
}

Bunu yapmanın daha iyi bir yolu olmalı. Bunun türünü kontrol edip Tsonra bir switchifade kullanmamın bir yolu var mı?


1
Her veri türü için özel olarak ne yaptığınızı kişisel olarak bilmek isterim. Her veri türü için aşağı yukarı aynı dönüşümü yapıyorsanız, farklı türleri ortak bir arabirime eşlemek ve bu arabirim üzerinde çalışmak daha kolay olabilir.
Juliet

Yanıtlar:


121

Aşırı yüklemeleri kullanabilirsiniz:

public static string BuildClause(List<string> l){...}

public static string BuildClause(List<int> l){...}

public static string BuildClause<T>(List<T> l){...}

Veya genel parametrenin türünü inceleyebilirsiniz:

Type listType = typeof(T);
if(listType == typeof(int)){...}

23
+1: Aşırı yüklenmeler, tasarım ve uzun vadeli bakım açısından kesinlikle en iyi çözümdür. Genel bir parametrenin çalışma zamanı tip kontrolü, düz bir yüzle kodlamak için çok ironik görünüyor.
Juliet

Bunun ne zaman yararlı olacağına dair harika bir örnek, çılgınca değişen türlere sahip genel serileştirmedir. İletilen nesne bir dizeyse, fazladan neden iş görür? Bir
dizeyse,

Pardon, bunu kullanmak switch-caseyerine kullanarak elde etmenin bir yolu var if-elsemı?
Tan

@HappyCoding maalesef değil = (bunu bir sonraki C # sürümüyle yapabilirsiniz.
jonnii

7
Aşırı yüklemeleriniz işlevsel olarak farklıysa (yan etkileri de göz önünde bulundurun) genel aşırı yüklemeye güvenmemelisiniz ( bu yanıta bakın ), çünkü daha özel bir aşırı yüklemenin çağrılacağını garanti edemezsiniz. Buradaki kural şudur: Eğer belirli bir tip için özel bir mantık yapmak ZORUNDA iseniz, o zaman o tipi kontrol etmeli ve aşırı yükleme kullanmamalısınız; ancak, yalnızca özel bir mantık yapmayı TERCİH EDİYORSANIZ (yani performans iyileştirmeleri için), ancak genel durum dahil tüm aşırı yüklemeler aynı sonucu verirse, o zaman tip kontrolü yerine aşırı yükleme kullanabilirsiniz.
tomosius

23

Kullanabilirsiniz typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}

7

Varsayılan olarak, harika bir yol olmadığını bilin. Bir süre önce bu konuda hayal kırıklığına uğradım ve biraz yardımcı olan ve sözdizimini biraz daha temiz hale getiren küçük bir yardımcı sınıf yazdım. Esasen kodu dönüştürür

TypeSwitcher.Do(clause[0],
  TypeSwitch.Case<int>(x => ...),  // x is an int
  TypeSwitch.Case<decimal>(d => ...), // d is a decimal 
  TypeSwitch.Case<string>(s => ...)); // s is a string

Tam blog yayını ve uygulamaya ilişkin ayrıntılar burada mevcuttur


6

Ve C # geliştiği için, (şimdi) desen eşleştirmeyi kullanabilirsiniz .

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        switch (clause[0])
        {
            case int x: // do something with x, which is an int here...
            case decimal x: // do something with x, which is a decimal here...
            case string x: // do something with x, which is a string here...
            ...
            default: throw new ApplicationException("Invalid type");
        }
    }
}

Ve yine C # 8.0'daki anahtar ifadeleriyle , sözdizimi daha da özlü hale geliyor.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        return clause[0] switch
        {
            int x => "some string related to this int",
            decimal x => "some string related to this decimal",
            string x => x,
            ...,
            _ => throw new ApplicationException("Invalid type")
        }
    }
}

4

Typeof operatörü ...

typeof(T)

... c # switch ifadesiyle çalışmaz. Peki buna ne dersiniz? Aşağıdaki gönderi statik bir sınıf içeriyor ...

'Yazıyı açmak' için bundan daha iyi bir alternatif var mı?

... bu, şu şekilde kod yazmanıza izin verir:

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

Ayrıca burada JaredPar'ın cevabına bakın.
Robert Harvey

3

Türleri kontrol etme ve türe göre bir şeyler yapmanın jenerikler için harika bir fikir olmadığını söyleyen herkes için aynı fikirdeyim ama bunun tamamen mantıklı olduğu bazı durumlar olabileceğini düşünüyorum.

Örneğin, bu şekilde uygulandığını söyleyen bir sınıfınız varsa (Not: Bu kodun basit olması için yaptığı her şeyi göstermiyorum ve buraya sadece kesip yapıştırdım, bu nedenle kodun tamamının yaptığı gibi tasarlanmayabilir veya çalışmayabilir ancak noktayı geçer. Ayrıca, Birim bir numaralandırmadır):

public class FoodCount<TValue> : BaseFoodCount
{
    public TValue Value { get; set; }

    public override string ToString()
    {
        if (Value is decimal)
        {
            // Code not cleaned up yet
            // Some code and values defined in base class

            mstrValue = Value.ToString();
            decimal mdecValue;
            decimal.TryParse(mstrValue, out mdecValue);

            mstrValue = decimal.Round(mdecValue).ToString();

            mstrValue = mstrValue + mstrUnitOfMeasurement;
            return mstrValue;
        }
        else
        {
            // Simply return a string
            string str = Value.ToString() + mstrUnitOfMeasurement;
            return str;
        }
    }
}

...

public class SaturatedFat : FoodCountWithDailyValue<decimal>
{
    public SaturatedFat()
    {
        mUnit = Unit.g;
    }

}

public class Fiber : FoodCount<int>
{
    public Fiber()
    {
        mUnit = Unit.g;
    }
}

public void DoSomething()
{
       nutritionFields.SaturatedFat oSatFat = new nutritionFields.SaturatedFat();

       string mstrValueToDisplayPreFormatted= oSatFat.ToString();
}

Özetle, özel bir şey yapmak için jeneriğin ne tür olduğunu kontrol etmek isteyebileceğiniz geçerli nedenler olduğunu düşünüyorum.


2

Switch ifadesini yapmasını istediğiniz şey için kullanmanın bir yolu yoktur. Switch deyimi, bir "Tür" nesnesi gibi karmaşık türleri veya bu konu için başka herhangi bir nesne türünü içermeyen integral türlerle sağlanmalıdır.


2

Yapınız genel bir yöntemin amacını tamamen ortadan kaldırıyor. Bilerek çirkin çünkü başarmaya çalıştığınız şeyi başarmanın daha iyi bir yolu olmalı, ancak bunun ne olduğunu anlamak için bize yeterince bilgi vermemişsiniz.


2

Yapabilirsiniz typeOf(T), ancak yönteminizi iki kez kontrol eder ve buradaki tek sorumluluğu ihlal etmediğinizden emin olurum. Bu bir kod kokusu olurdu ve bu yapılmaması gerektiği anlamına gelmez, ancak dikkatli olmanız gerekir.

Jeneriklerin amacı, türün ne olduğu umrunda değilken veya belirli bir kriterler kümesine uyduğu sürece türden bağımsız algortimler oluşturabilmektir. Uygulamanız çok genel değil.


2

Umarım bunu faydalı bulursunuz:

  • typeof(IList<T>).IsGenericType == true
  • typeof(IList<T>).GetGenericTypeDefinition() == typeof(IList<>)
  • typeof(IList<int>).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt


0

Buna ne dersin :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
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.