มีข้อ จำกัด ที่ จำกัด วิธีการทั่วไปของฉันเป็นประเภทตัวเลขหรือไม่?


364

ทุกคนสามารถบอกฉันว่ามีวิธีที่มี generics เพื่อ จำกัด การโต้แย้งประเภททั่วไปTเพียง:

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64

ฉันตระหนักถึงwhereคำหลัก แต่ไม่สามารถหาอินเตอร์เฟซสำหรับเพียงประเภทเหล่านี้

สิ่งที่ต้องการ:

static bool IntegerFunction<T>(T value) where T : INumeric 

คำตอบ:


140

C # ไม่รองรับสิ่งนี้ Hejlsberg ได้อธิบายถึงเหตุผลที่ไม่ใช้คุณลักษณะนี้ในการสัมภาษณ์กับ Bruce Eckel :

และยังไม่ชัดเจนว่าความซับซ้อนที่เพิ่มเข้ามานั้นคุ้มค่ากับผลตอบแทนเล็กน้อยที่คุณได้รับ หากสิ่งที่คุณต้องการทำไม่ได้รับการสนับสนุนโดยตรงในระบบข้อ จำกัด คุณสามารถทำได้ด้วยรูปแบบจากโรงงาน คุณสามารถมีMatrix<T>ตัวอย่างเช่นและMatrixคุณต้องการที่จะกำหนดวิธีการจุดผลิตภัณฑ์ แน่นอนว่านั่นหมายความว่าคุณต้องในท้ายที่สุดจะเข้าใจวิธีการคูณสองTs แต่คุณไม่สามารถพูดได้ว่าเป็นข้อ จำกัด อย่างน้อยไม่ได้ถ้าTเป็นint, หรือdouble floatแต่สิ่งที่คุณจะทำคือมีคุณMatrixใช้เป็นอาร์กิวเมนต์Calculator<T>และมีวิธีการที่เรียกว่าCalculator<T> คุณไปใช้ที่และคุณจะผ่านมันไปmultiplyMatrix

อย่างไรก็ตามสิ่งนี้นำไปสู่รหัสที่ค่อนข้างซับซ้อนซึ่งผู้ใช้ต้องจัดหาCalculator<T>การใช้งานของตนเองสำหรับแต่ละTสิ่งที่พวกเขาต้องการใช้ ตราบใดที่มันไม่จำเป็นต้องขยายออกไปเช่นถ้าคุณแค่ต้องการสนับสนุนประเภทจำนวนคงที่เช่นintและdoubleคุณสามารถหนีไปได้ด้วยอินเทอร์เฟซที่ค่อนข้างง่าย:

var mat = new Matrix<int>(w, h);

( ใช้งานน้อยที่สุดใน GitHub Gist )

อย่างไรก็ตามทันทีที่คุณต้องการให้ผู้ใช้สามารถจัดหาประเภทที่กำหนดเองของคุณเองคุณจะต้องเปิดการใช้งานนี้เพื่อให้ผู้ใช้สามารถจัดหาCalculatorอินสแตนซ์ของตนเองได้ ตัวอย่างเช่นหากต้องการสร้างอินสแตนซ์ของเมทริกซ์ที่ใช้จุดทศนิยมแบบทศนิยมที่กำหนดเองDFPคุณต้องเขียนโค้ดนี้:

var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);

…และนำสมาชิกทุกคนDfpCalculator : ICalculator<DFP>ไปปฏิบัติ

ทางเลือกที่โชคร้ายหุ้นข้อ จำกัด เดียวกันคือการทำงานกับการเรียนนโยบายตามที่กล่าวไว้ในคำตอบของ Sergey Shandar


25
btw, MiscUtil ให้คลาสทั่วไปที่ทำสิ่งนี้; Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

1
@ Mark: ความคิดเห็นที่ดี อย่างไรก็ตามเพื่อให้ชัดเจนฉันไม่คิดว่า Hejlsberg อ้างถึงการสร้างรหัสเป็นวิธีแก้ปัญหาเช่นเดียวกับที่คุณทำในOperator<T>รหัส (เนื่องจากการสัมภาษณ์ได้รับมานานก่อนการมีอยู่ของExpressionsกรอบงานแม้ว่าจะทำได้ การใช้งานแน่นอนReflection.Emit) - และฉันต้องการจะจริงๆสนใจในของเขาแก้ปัญหา
Konrad Rudolph

@ Konrad Rudolph: ฉันคิดว่าคำตอบสำหรับคำถามที่คล้ายกันนี้อธิบายถึงวิธีแก้ปัญหาของ Hejlsberg ชั้นสามัญอื่น ๆ ทำบทคัดย่อ เนื่องจากต้องการให้คุณใช้คลาสทั่วไปอื่น ๆ สำหรับแต่ละประเภทที่คุณต้องการสนับสนุนมันจะส่งผลให้เกิดรหัสซ้ำกัน แต่หมายความว่าคุณสามารถสร้างอินสแตนซ์ของคลาสทั่วไปที่เป็นประเภทที่สนับสนุนได้เท่านั้น
Ergwun

14
ฉันไม่เห็นด้วยกับวลีของ Heijsberg "ในแง่หนึ่งเทมเพลต C ++ นั้นไม่ได้พิมพ์ออกมาจริง ๆ หรือพิมพ์อย่างหลวม ๆ ในขณะที่ C # generics พิมพ์ดีดอย่างแรง" นั่นคือการตลาด BS จริง ๆ เพื่อส่งเสริม C # Strong / Weak-typing ไม่ได้เกี่ยวข้องกับคุณภาพการวินิจฉัย มิฉะนั้น: ค้นหาที่น่าสนใจ
เซบาสเตียนมัค

100

เมื่อพิจารณาถึงความนิยมของคำถามนี้และความสนใจในฟังก์ชั่นดังกล่าวฉันประหลาดใจที่เห็นว่ายังไม่มีคำตอบสำหรับ T4

ในโค้ดตัวอย่างนี้ฉันจะสาธิตตัวอย่างง่าย ๆ ว่าคุณสามารถใช้เครื่องมือสร้างพลังอันทรงพลังเพื่อทำสิ่งที่คอมไพเลอร์ทำอะไรได้บ้างเบื้องหลังเบื้องหลังกับยาชื่อสามัญ

แทนที่จะต้องห่วงและเสียสละเวลาแน่นอนคุณสามารถสร้างฟังก์ชั่นที่คุณต้องการสำหรับทุกประเภทที่คุณชอบและใช้มันตามนั้น (ในเวลารวบรวม!)

เพื่อที่จะทำสิ่งนี้:

  • สร้างใหม่เทมเพลตข้อความไฟล์ที่เรียกว่าGenericNumberMethodTemplate.tt
  • ลบรหัสที่สร้างขึ้นอัตโนมัติ (คุณจะเก็บไว้เป็นส่วนใหญ่ แต่ก็ไม่จำเป็นต้องใช้บ้าง)
  • เพิ่มตัวอย่างต่อไปนี้:
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>

<# Type[] types = new[] {
    typeof(Int16), typeof(Int32), typeof(Int64),
    typeof(UInt16), typeof(UInt32), typeof(UInt64)
    };
#>

using System;
public static class MaxMath {
    <# foreach (var type in types) { 
    #>
        public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
            return val1 > val2 ? val1 : val2;
        }
    <#
    } #>
}

แค่นั้นแหละ. คุณทำเสร็จแล้ว

การบันทึกไฟล์นี้จะรวบรวมโดยอัตโนมัติไปยังไฟล์ต้นฉบับ:

using System;
public static class MaxMath {
    public static Int16 Max (Int16 val1, Int16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int32 Max (Int32 val1, Int32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int64 Max (Int64 val1, Int64 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt16 Max (UInt16 val1, UInt16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt32 Max (UInt32 val1, UInt32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt64 Max (UInt64 val1, UInt64 val2) {
        return val1 > val2 ? val1 : val2;
    }
}

ในmainวิธีการของคุณคุณสามารถตรวจสอบว่าคุณมีความแน่นอนเวลารวบรวม:

namespace TTTTTest
{
    class Program
    {
        static void Main(string[] args)
        {
            long val1 = 5L;
            long val2 = 10L;
            Console.WriteLine(MaxMath.Max(val1, val2));
            Console.Read();
        }
    }
}

ป้อนคำอธิบายรูปภาพที่นี่

ฉันจะได้ข้อสังเกตล่วงหน้าหนึ่งข้อ: ไม่นี่ไม่ใช่การละเมิดหลักการ DRY หลักการ DRY อยู่ที่นั่นเพื่อป้องกันไม่ให้ผู้คนทำซ้ำรหัสในหลาย ๆ ที่ซึ่งจะทำให้แอปพลิเคชันยากที่จะรักษา

นี่ไม่ใช่ทุกกรณีที่นี่: ถ้าคุณต้องการการเปลี่ยนแปลงคุณสามารถเปลี่ยนเทมเพลต (แหล่งเดียวสำหรับทุกรุ่นของคุณ!) และมันก็เสร็จแล้ว

เพื่อที่จะใช้กับคำจำกัดความของคุณเองเพิ่มประกาศ Namespace (ให้แน่ใจว่ามันเป็นคนเดียวกับที่คุณจะกำหนดดำเนินการของคุณเอง) partialเพื่อสร้างรหัสของคุณและทำเครื่องหมายระดับเป็น หลังจากนั้นเพิ่มบรรทัดเหล่านี้ลงในไฟล์เทมเพลตของคุณเพื่อรวมไว้ในการรวบรวมในที่สุด:

<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>

มาพูดกันเถอะ: นี่มันเท่ห์ดี

Disclaimer: ตัวอย่างนี้ได้รับอิทธิพลอย่างมากจากmetaprogramming ใน .NET โดยเควิน Hazzard และเจสัน Bock นิงสิ่งพิมพ์


นี่มันเจ๋งมาก แต่มันจะเป็นไปได้ไหมที่จะปรับเปลี่ยนวิธีนี้เพื่อให้วิธีการยอมรับประเภททั่วไปบางอย่างTที่สืบทอดหรือสืบทอดมาจากIntXคลาสต่าง ๆ? ฉันชอบโซลูชันนี้เพราะมันช่วยประหยัดเวลา แต่มันถึง 100% แก้ปัญหาได้ (แม้จะไม่ดีเท่ากับว่า C # มีการรองรับข้อ จำกัด ประเภทนี้ในตัว) แต่ละวิธีที่สร้างขึ้นควรเป็นวิธีทั่วไป พวกเขาสามารถส่งคืนวัตถุประเภทที่สืบทอดจากหนึ่งในIntXXชั้นเรียน
Zachary Kniebel

1
@ZacharyKniebel ที่: IntXXประเภท structs ซึ่งหมายความว่าพวกเขาจะไม่สนับสนุนการสืบทอดในสถานที่แรก และแม้ว่ามันจะทำเช่นนั้นหลักการทดแทน Liskov (ซึ่งคุณอาจรู้จากสำนวน SOLID) ใช้: หากวิธีการที่ถูกกำหนดเป็นXและYเป็นลูกของXแล้วต่อความหมายใด ๆYควรจะสามารถส่งผ่านไปยังวิธีการแทน ประเภทฐานของมัน
Jeroen Vannevel

1
วิธีแก้ปัญหานี้ใช้นโยบายstackoverflow.com/questions/32664/…ใช้ T4 เพื่อสร้างคลาส
Sergey Shandar

2
+1 สำหรับโซลูชันนี้เนื่องจากจะรักษาประสิทธิภาพการทำงานของประเภทอินทิกรัลในตัวซึ่งแตกต่างจากโซลูชันตามนโยบาย การเรียกตัวดำเนินการ CLR ในตัว (เช่นเพิ่ม) ผ่านวิธีเพิ่มเติม (อาจเสมือน) อาจส่งผลกระทบอย่างรุนแรงต่อประสิทธิภาพการทำงานหากใช้หลายครั้ง (เช่นในห้องสมุดทางคณิตศาสตร์) และเนื่องจากจำนวนชนิดอินทิกรัลเป็นค่าคงที่ (และไม่สามารถสืบทอดได้) คุณจะต้องสร้างรหัสใหม่เพื่อแก้ไขข้อบกพร่อง
Attila Klenik

1
เจ๋งมากและฉันเพิ่งจะเริ่มใช้มันแล้วฉันจำได้ว่าขึ้นอยู่กับ Resharper ฉันสำหรับ refactoring และคุณไม่สามารถเปลี่ยนชื่อ refactor ผ่านเทมเพลต T4 มันไม่สำคัญ แต่ก็คุ้มค่าที่จะพิจารณา
bradgonesurfing

86

ไม่มีข้อ จำกัด สำหรับเรื่องนี้ มันเป็นปัญหาที่แท้จริงสำหรับทุกคนที่ต้องการใช้ยาสามัญสำหรับการคำนวณตัวเลข

ฉันจะไปต่อและบอกว่าเราต้องการ

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

หรือแม้กระทั่ง

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

น่าเสียดายที่คุณมีเฉพาะอินเทอร์เฟซคลาสพื้นฐานและคำหลักstruct(ต้องเป็นประเภทค่า) class(ต้องเป็นประเภทอ้างอิง) และnew()(ต้องมีคอนสตรัคเตอร์เริ่มต้น)

คุณสามารถห่อจำนวนในสิ่งอื่น (คล้ายกับINullable<T>) เช่นที่นี่ใน codeproject


คุณสามารถใช้ข้อ จำกัด ที่รันไทม์ (โดยสะท้อนให้เห็นถึงผู้ประกอบการหรือการตรวจสอบประเภท) แต่ที่จะสูญเสียความได้เปรียบของการมีทั่วไปในสถานที่แรก


2
ฉันสงสัยว่าคุณเคยเห็นการสนับสนุนของ MiscUtil สำหรับผู้ให้บริการทั่วไปหรือไม่ ... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

10
ใช่ - Jon Skeet ชี้ให้ฉันดูพวกเขาเป็นเวลานานแล้ว (แต่หลังจากการตอบสนองในปีนี้) - พวกเขาเป็นความคิดที่ฉลาด แต่ฉันก็ยังต้องการการสนับสนุนที่เหมาะสม
Keith

1
เดี๋ยวก่อนwhere T : operators( +, -, /, * )ถูกกฎหมาย C # หรือไม่ ขออภัยสำหรับคำถามที่สมัครใหม่
kdbanman

@kdbanman ฉันไม่คิดอย่างนั้น Keith บอกว่า C # ไม่สนับสนุนสิ่งที่ OP ขอและแนะนำว่าเราควรจะทำได้where T : operators( +, -, /, * )แต่ทำไม่ได้
AMTerp

62

วิธีแก้ปัญหาโดยใช้นโยบาย:

interface INumericPolicy<T>
{
    T Zero();
    T Add(T a, T b);
    // add more functions here, such as multiplication etc.
}

struct NumericPolicies:
    INumericPolicy<int>,
    INumericPolicy<long>
    // add more INumericPolicy<> for different numeric types.
{
    int INumericPolicy<int>.Zero() { return 0; }
    long INumericPolicy<long>.Zero() { return 0; }
    int INumericPolicy<int>.Add(int a, int b) { return a + b; }
    long INumericPolicy<long>.Add(long a, long b) { return a + b; }
    // implement all functions from INumericPolicy<> interfaces.

    public static NumericPolicies Instance = new NumericPolicies();
}

ขั้นตอนวิธีการ:

static class Algorithms
{
    public static T Sum<P, T>(this P p, params T[] a)
        where P: INumericPolicy<T>
    {
        var r = p.Zero();
        foreach(var i in a)
        {
            r = p.Add(r, i);
        }
        return r;
    }

}

การใช้งาน:

int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.

การแก้ปัญหาคือการรวบรวมเวลาที่ปลอดภัย CityLizard Framework เป็นรุ่นที่รวบรวมสำหรับ. NET 4.0 ไฟล์ดังกล่าวเป็น lib / NETFramework4.0 / CityLizard.Policy.dll

นอกจากนี้ยังมีอยู่ใน Nuget: https://www.nuget.org/packages/CityLizard/ ดูโครงสร้างCityLizard.Policy.I


ฉันมีปัญหากับรูปแบบนี้เมื่อมีอาร์กิวเมนต์ของฟังก์ชันน้อยกว่าพารามิเตอร์ทั่วไป stackoverflow.com/questions/36048248/ ที่
2559

เหตุผลว่าทำไมใช้struct? ถ้าฉันใช้คลาสซิงเกิลแทนและเปลี่ยนอินสแตนซ์เป็นpublic static NumericPolicies Instance = new NumericPolicies();แล้วเพิ่มตัวสร้างprivate NumericPolicies() { }นี้
M.kazem Akhgary

@ M.kazemAkhgary คุณสามารถใช้ซิงเกิล ฉันชอบ struct ในทางทฤษฎีมันสามารถปรับให้เหมาะสมโดยคอมไพเลอร์ / CLR เพราะ struct ไม่มีข้อมูล ในกรณีของซิงเกิลคุณจะยังคงผ่านการอ้างอิงซึ่งอาจเพิ่มแรงกดดันต่อ GC ข้อดีอีกข้อคือโครงสร้างไม่สามารถเป็นโมฆะ :-)
Sergey Shandar

ฉันจะบอกว่าคุณพบโซลูชันที่ชาญฉลาดมาก แต่โซลูชันนั้น จำกัด เกินไปสำหรับฉัน: ฉันจะใช้มันในT Add<T> (T t1, T t2)แต่Sum()จะใช้ได้เมื่อสามารถดึง T ประเภทของตัวเองได้จากพารามิเตอร์ซึ่งไม่สามารถทำได้ เมื่อมันฝังอยู่ในฟังก์ชั่นทั่วไปอื่น
Tobias Knauss

16

คำถามนี้เป็นคำถามที่พบบ่อยเล็กน้อยดังนั้นฉันจึงโพสต์สิ่งนี้เป็น wiki (เนื่องจากฉันเคยโพสต์ที่คล้ายกันมาก่อน แต่นี่เป็นคำถามที่เก่ากว่า); อย่างไรก็ตาม...

คุณใช้. NET เวอร์ชันใดอยู่ หากคุณใช้. NET 3.5 ฉันมีการใช้งานตัวดำเนินการทั่วไปในMiscUtil (ฟรีเป็นต้น)

นี่เป็นวิธีการT Add<T>(T x, T y)และตัวแปรอื่น ๆ สำหรับการคำนวณทางคณิตศาสตร์ในประเภทต่างๆ (เช่นDateTime + TimeSpan)

นอกจากนี้ยังใช้งานได้กับตัวดำเนินการ inbuilt, lift และ bespoke ทั้งหมดและแคชตัวแทนเพื่อประสิทธิภาพ

บางหลังเพิ่มเติมเกี่ยวกับเหตุผลนี้เป็นเรื่องยุ่งยากเป็นที่นี่

คุณอาจต้องการทราบว่าdynamic(4.0) การเรียงลำดับของการแก้ปัญหานี้โดยทางอ้อมเช่น -

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

14

น่าเสียดายที่คุณสามารถระบุ struct ในส่วนคำสั่ง where ในอินสแตนซ์นี้เท่านั้น ดูเหมือนจะแปลกที่คุณไม่สามารถระบุ Int16, Int32 และอื่น ๆ ได้โดยเฉพาะ แต่ฉันแน่ใจว่ามีเหตุผลในการใช้งานอย่างลึกล้ำซึ่งเป็นการตัดสินใจที่จะไม่อนุญาตให้ใช้ประเภทค่าในส่วนคำสั่ง

ฉันเดาทางออกเดียวคือการตรวจสอบ runtime ซึ่งน่าเสียดายที่ป้องกันปัญหาที่ถูกหยิบขึ้นมาในเวลารวบรวม นั่นเป็นสิ่งที่ต้องการ: -

static bool IntegerFunction<T>(T value) where T : struct {
  if (typeof(T) != typeof(Int16)  &&
      typeof(T) != typeof(Int32)  &&
      typeof(T) != typeof(Int64)  &&
      typeof(T) != typeof(UInt16) &&
      typeof(T) != typeof(UInt32) &&
      typeof(T) != typeof(UInt64)) {
    throw new ArgumentException(
      string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
  }

  // Rest of code...
}

ซึ่งฉันรู้ว่าน่าเกลียดเล็กน้อย แต่อย่างน้อยก็มีข้อ จำกัด ตามที่กำหนด

ฉันจะพิจารณาถึงผลกระทบด้านประสิทธิภาพที่เป็นไปได้สำหรับการใช้งานนี้ซึ่งอาจมีวิธีที่เร็วกว่า


13
+1 อย่างไรก็ตาม// Rest of code...อาจไม่รวบรวมหากขึ้นอยู่กับการดำเนินการที่กำหนดโดยข้อ จำกัด
Nick

1
Convert.ToIntXX (ค่า) อาจช่วยรวบรวม "// ส่วนที่เหลือของรหัส" อย่างน้อยก็จนกว่าประเภทการคืนค่าของ IntegerFunction ก็เป็นประเภท T เช่นกัน :-p
yoyo

-1; สิ่งนี้ใช้ไม่ได้ด้วยเหตุผลที่ได้รับจาก @Nick ทันทีที่คุณพยายามดำเนินการทางคณิตศาสตร์ใด ๆ ใน// Rest of code...ลักษณะvalue + valueหรือvalue * valueคุณมีข้อผิดพลาดในการรวบรวม
Mark Amery

13

อาจใกล้เคียงที่สุดที่คุณสามารถทำได้คือ

static bool IntegerFunction<T>(T value) where T: struct

ไม่แน่ใจว่าคุณสามารถทำสิ่งต่อไปนี้ได้หรือไม่

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>

สำหรับบางสิ่งที่เฉพาะเจาะจงทำไมไม่เพียง แต่มีโอเวอร์โหลดสำหรับแต่ละประเภทรายการสั้นมากและอาจมีหน่วยความจำน้อยกว่า


6

เริ่มต้นด้วย C # 7.3 คุณสามารถใช้ใกล้ชิดประมาณ - The ข้อ จำกัด ที่ไม่มีการจัดการเพื่อระบุว่าพารามิเตอร์ชนิดไม่ใช่ตัวชี้ไม่ใช่ nullable ไม่มีการจัดการประเภท

class SomeGeneric<T> where T : unmanaged
{
//...
}

ข้อ จำกัด ที่ไม่มีการจัดการแสดงถึงข้อ จำกัด ของโครงสร้างและไม่สามารถรวมกับข้อ จำกัด ของโครงสร้างหรือข้อ จำกัด ใหม่ ()

ประเภทเป็นประเภทที่ไม่มีการจัดการหากเป็นประเภทใดประเภทหนึ่งต่อไปนี้:

  • sbyte, ไบต์, สั้น, ushort, int, uint, ยาว, ulong, ถ่าน, ลอยตัว, สองเท่า, ทศนิยมหรือบูล
  • ชนิด enum ใด ๆ
  • ตัวชี้ประเภทใดก็ได้
  • ประเภทโครงสร้างที่ผู้ใช้กำหนดเองใด ๆ ที่มีเขตข้อมูลประเภทที่ไม่มีการจัดการเท่านั้นและใน C # 7.3 และรุ่นก่อนหน้านั้นไม่ใช่ประเภทที่สร้างขึ้น (ประเภทที่มีอาร์กิวเมนต์ประเภทอย่างน้อยหนึ่งรายการ)

เพื่อ จำกัด เพิ่มเติมและกำจัดตัวชี้และชนิดที่ผู้ใช้กำหนดเองที่ไม่ใช้ IComparable เพิ่มIComparable (แต่ enum ยังคงได้มาจาก IComparable ดังนั้น จำกัด enum โดยเพิ่ม IEquatable <T> คุณสามารถไปต่อได้ขึ้นอยู่กับสถานการณ์ของคุณและเพิ่มอินเทอร์เฟซเพิ่มเติม ไม่มีการจัดการช่วยให้รายการนี้สั้นกว่า):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }

ดี แต่ไม่พอ ... ยกตัวอย่างเช่นDateTimeอยู่ภายใต้unmanaged, IComparable, IEquatable<T>ข้อ จำกัด ..
Adam Calvet Bohl

ฉันรู้ แต่คุณสามารถไปต่อได้ขึ้นอยู่กับสถานการณ์ของคุณและเพิ่มอินเทอร์เฟซเพิ่มเติม ไม่มีการจัดการช่วยให้รายการนี้สั้นลง ฉันเพิ่งจะแสดงวิธีการประมาณโดยใช้การจัดการ สำหรับกรณีส่วนใหญ่นี่ก็เพียงพอแล้ว
Vlad Novakovsky

4

ไม่มีวิธีในการ จำกัด แม่แบบให้กับประเภท แต่คุณสามารถกำหนดการกระทำที่แตกต่างกันตามประเภท ในฐานะที่เป็นส่วนหนึ่งของแพคเกจตัวเลขทั่วไปฉันต้องการคลาสทั่วไปเพื่อเพิ่มสองค่า

    class Something<TCell>
    {
        internal static TCell Sum(TCell first, TCell second)
        {
            if (typeof(TCell) == typeof(int))
                return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));

            if (typeof(TCell) == typeof(double))
                return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));

            return second;
        }
    }

โปรดทราบว่า typeofs จะถูกประเมิน ณ เวลาคอมไพล์ดังนั้นหากคำสั่งจะถูกลบออกโดยคอมไพเลอร์ คอมไพเลอร์ยังกำจัด casts ปลอม ดังนั้นบางสิ่งจะแก้ไขในคอมไพเลอร์ถึง

        internal static int Sum(int first, int second)
        {
            return first + second;
        }

ขอขอบคุณที่ให้การแก้ปัญหาเชิงประจักษ์!
zsf222

มันไม่เหมือนกันกับการสร้างวิธีการเดียวกันสำหรับแต่ละประเภทหรือไม่
Luis

3

ฉันสร้างฟังก์ชันห้องสมุดเล็กน้อยเพื่อแก้ปัญหาเหล่านี้:

แทน:

public T DifficultCalculation<T>(T a, T b)
{
    T result = a * b + a; // <== WILL NOT COMPILE!
    return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.

คุณสามารถเขียน:

public T DifficultCalculation<T>(Number<T> a, Number<T> b)
{
    Number<T> result = a * b + a;
    return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.

คุณสามารถค้นหาซอร์สโค้ดได้ที่นี่: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number หมายเลข


2

ฉันสงสัยเหมือนกับ samjudson ทำไมต้องเป็นจำนวนเต็มเท่านั้น และถ้าเป็นเช่นนั้นคุณอาจต้องการสร้างคลาสผู้ช่วยเหลือหรืออะไรทำนองนั้นเพื่อเก็บทุกประเภทที่คุณต้องการ

หากสิ่งที่คุณต้องการเป็นจำนวนเต็มอย่าใช้ generic นั่นไม่ใช่ generic หรือดีกว่ายังปฏิเสธประเภทอื่น ๆ โดยตรวจสอบประเภทของมัน


2

ยังไม่มีวิธีแก้ปัญหา 'ดี' สำหรับสิ่งนี้ อย่างไรก็ตามคุณสามารถ จำกัด ประเภทอาร์กิวเมนต์ให้แคบลงเพื่อตัดการใส่มิสชันจำนวนมากสำหรับข้อ จำกัด 'ใส่ตัวเลข' ของคุณตามที่ Haacked แสดงไว้ด้านบน

Bool แบบคงที่ IntegerFunction <T> (ค่า T) โดยที่ T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...


2

หากคุณใช้. NET 4.0 และใหม่กว่าคุณสามารถใช้ไดนามิกเป็นอาร์กิวเมนต์ของเมธอดและตรวจสอบในรันไทม์ที่ประเภทอาร์กิวเมนต์แบบไดนามิกที่ผ่านเป็นชนิดตัวเลข / จำนวนเต็ม

ถ้าชนิดของผ่านแบบไดนามิกคือไม่ได้พิมพ์ / จำนวนเต็มตัวเลขแล้วโยนข้อยกเว้น

ตัวอย่างรหัสย่อที่ใช้ความคิดนั้นเป็นดังนี้:

using System;
public class InvalidArgumentException : Exception
{
    public InvalidArgumentException(string message) : base(message) {}
}
public class InvalidArgumentTypeException : InvalidArgumentException
{
    public InvalidArgumentTypeException(string message) : base(message) {}
}
public class ArgumentTypeNotIntegerException : InvalidArgumentTypeException
{
    public ArgumentTypeNotIntegerException(string message) : base(message) {}
}
public static class Program
{
    private static bool IntegerFunction(dynamic n)
    {
        if (n.GetType() != typeof(Int16) &&
            n.GetType() != typeof(Int32) &&
            n.GetType() != typeof(Int64) &&
            n.GetType() != typeof(UInt16) &&
            n.GetType() != typeof(UInt32) &&
            n.GetType() != typeof(UInt64))
            throw new ArgumentTypeNotIntegerException("argument type is not integer type");
        //code that implements IntegerFunction goes here
    }
    private static void Main()
    {
         Console.WriteLine("{0}",IntegerFunction(0)); //Compiles, no run time error and first line of output buffer is either "True" or "False" depends on the code that implements "Program.IntegerFunction" static method.
         Console.WriteLine("{0}",IntegerFunction("string")); //Also compiles but it is run time error and exception of type "ArgumentTypeNotIntegerException" is thrown here.
         Console.WriteLine("This is the last Console.WriteLine output"); //Never reached and executed due the run time error and the exception thrown on the second line of Program.Main static method.
    }

แน่นอนว่าโซลูชันนี้ทำงานในเวลาทำงานเท่านั้น แต่ไม่รวมเวลา

หากคุณต้องการโซลูชันที่ทำงานในเวลาคอมไพล์และไม่เคยใช้งานในเวลานั้นคุณจะต้องล้อมรอบไดนามิกด้วยโครงสร้างสาธารณะ / คลาสที่มีตัวสร้างสาธารณะมากเกินไปรับข้อโต้แย้งของประเภทที่ต้องการเท่านั้นและให้ชื่อที่เหมาะสม struct / คลาส

มันทำให้รู้สึกว่าพลวัตที่ห่อไว้นั้นเป็นสมาชิกส่วนตัวของคลาส / struct เสมอและเป็นสมาชิกเพียงคนเดียวของ struct / class และชื่อของสมาชิกเพียงคนเดียวของ struct / class คือ "value"

คุณจะต้องกำหนดและใช้วิธีสาธารณะและ / หรือตัวดำเนินการที่ทำงานกับชนิดที่ต้องการสำหรับสมาชิกแบบไดนามิกส่วนตัวของคลาส / โครงสร้างถ้าจำเป็น

นอกจากนี้ยังทำให้รู้สึกว่า struct / class มีตัวสร้างพิเศษ / ไม่ซ้ำกันที่ยอมรับไดนามิกเป็นอาร์กิวเมนต์ที่เริ่มต้นมันเป็นเพียงสมาชิกแบบไดนามิกส่วนบุคคลที่เรียกว่า "ค่า" แต่ตัวดัดแปลงของตัวสร้างนี้เป็นส่วนตัวแน่นอน

เมื่อคลาส / struct พร้อมที่จะกำหนดชนิดของอาร์กิวเมนต์ของ IntegerFunction ให้เป็นคลาส / โครงสร้างนั้นที่ได้ถูกกำหนดไว้

ตัวอย่างโค้ดขนาดยาวที่ใช้ความคิดนั้นเป็นอย่างไร:

using System;
public struct Integer
{
    private dynamic value;
    private Integer(dynamic n) { this.value = n; }
    public Integer(Int16 n) { this.value = n; }
    public Integer(Int32 n) { this.value = n; }
    public Integer(Int64 n) { this.value = n; }
    public Integer(UInt16 n) { this.value = n; }
    public Integer(UInt32 n) { this.value = n; }
    public Integer(UInt64 n) { this.value = n; }
    public Integer(Integer n) { this.value = n.value; }
    public static implicit operator Int16(Integer n) { return n.value; }
    public static implicit operator Int32(Integer n) { return n.value; }
    public static implicit operator Int64(Integer n) { return n.value; }
    public static implicit operator UInt16(Integer n) { return n.value; }
    public static implicit operator UInt32(Integer n) { return n.value; }
    public static implicit operator UInt64(Integer n) { return n.value; }
    public static Integer operator +(Integer x, Int16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int64 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt64 y) { return new Integer(x.value + y); }
    public static Integer operator -(Integer x, Int16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int64 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt64 y) { return new Integer(x.value - y); }
    public static Integer operator *(Integer x, Int16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int64 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt64 y) { return new Integer(x.value * y); }
    public static Integer operator /(Integer x, Int16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int64 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt64 y) { return new Integer(x.value / y); }
    public static Integer operator %(Integer x, Int16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int64 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt64 y) { return new Integer(x.value % y); }
    public static Integer operator +(Integer x, Integer y) { return new Integer(x.value + y.value); }
    public static Integer operator -(Integer x, Integer y) { return new Integer(x.value - y.value); }
    public static Integer operator *(Integer x, Integer y) { return new Integer(x.value * y.value); }
    public static Integer operator /(Integer x, Integer y) { return new Integer(x.value / y.value); }
    public static Integer operator %(Integer x, Integer y) { return new Integer(x.value % y.value); }
    public static bool operator ==(Integer x, Int16 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int16 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int32 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int32 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int64 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int64 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt16 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt16 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt32 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt32 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt64 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt64 y) { return x.value != y; }
    public static bool operator ==(Integer x, Integer y) { return x.value == y.value; }
    public static bool operator !=(Integer x, Integer y) { return x.value != y.value; }
    public override bool Equals(object obj) { return this == (Integer)obj; }
    public override int GetHashCode() { return this.value.GetHashCode(); }
    public override string ToString() { return this.value.ToString(); }
    public static bool operator >(Integer x, Int16 y) { return x.value > y; }
    public static bool operator <(Integer x, Int16 y) { return x.value < y; }
    public static bool operator >(Integer x, Int32 y) { return x.value > y; }
    public static bool operator <(Integer x, Int32 y) { return x.value < y; }
    public static bool operator >(Integer x, Int64 y) { return x.value > y; }
    public static bool operator <(Integer x, Int64 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt16 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt16 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt32 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt32 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt64 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt64 y) { return x.value < y; }
    public static bool operator >(Integer x, Integer y) { return x.value > y.value; }
    public static bool operator <(Integer x, Integer y) { return x.value < y.value; }
    public static bool operator >=(Integer x, Int16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Integer y) { return x.value >= y.value; }
    public static bool operator <=(Integer x, Integer y) { return x.value <= y.value; }
    public static Integer operator +(Int16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator -(Int16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator *(Int16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator /(Int16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator %(Int16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int64 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt64 x, Integer y) { return new Integer(x % y.value); }
    public static bool operator ==(Int16 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int16 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int32 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int32 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int64 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int64 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt16 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt16 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt32 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt32 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt64 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt64 x, Integer y) { return x != y.value; }
    public static bool operator >(Int16 x, Integer y) { return x > y.value; }
    public static bool operator <(Int16 x, Integer y) { return x < y.value; }
    public static bool operator >(Int32 x, Integer y) { return x > y.value; }
    public static bool operator <(Int32 x, Integer y) { return x < y.value; }
    public static bool operator >(Int64 x, Integer y) { return x > y.value; }
    public static bool operator <(Int64 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt16 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt16 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt32 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt32 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt64 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt64 x, Integer y) { return x < y.value; }
    public static bool operator >=(Int16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int64 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt64 x, Integer y) { return x <= y.value; }
}
public static class Program
{
    private static bool IntegerFunction(Integer n)
    {
        //code that implements IntegerFunction goes here
        //note that there is NO code that checks the type of n in rum time, because it is NOT needed anymore 
    }
    private static void Main()
    {
        Console.WriteLine("{0}",IntegerFunction(0)); //compile error: there is no overloaded METHOD for objects of type "int" and no implicit conversion from any object, including "int", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer(0))); //both compiles and no run time error
        Console.WriteLine("{0}",IntegerFunction("string")); //compile error: there is no overloaded METHOD for objects of type "string" and no implicit conversion from any object, including "string", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer("string"))); //compile error: there is no overloaded CONSTRUCTOR for objects of type "string"
    }
}

โปรดทราบว่าในการใช้ไดนามิกในรหัสของคุณคุณต้องเพิ่มการอ้างอิงถึงMicrosoft.CSharp

ถ้าเวอร์ชันของ. NET Framework ต่ำกว่า / ภายใต้ / น้อยกว่า 4.0 และไดนามิกไม่ได้ถูกกำหนดในรุ่นนั้นคุณจะต้องใช้วัตถุแทนและทำการแคสติ้งเป็นประเภทจำนวนเต็มซึ่งเป็นปัญหาดังนั้นฉันขอแนะนำให้คุณใช้ที่ .NET น้อย 4.0 หรือใหม่กว่าถ้าคุณสามารถเพื่อให้คุณสามารถใช้แบบไดนามิกแทนวัตถุ


2

น่าเสียดาย. NET ไม่ได้มีวิธีการทำเช่นนั้น

ในการแก้ไขปัญหานี้ฉันได้สร้างGenumerics ไลบรารี่ OSS ซึ่งให้การดำเนินงานตัวเลขมาตรฐานส่วนใหญ่สำหรับประเภทตัวเลขในตัวต่อไปนี้และรายการเทียบเท่าที่ไม่สามารถใช้ได้ซึ่งมีความสามารถในการเพิ่มการรองรับสำหรับประเภทตัวเลขอื่น ๆ

sbyte, byte, short, ushort, int, uint, long, ulong, float, double, decimalและBigInteger

ประสิทธิภาพเทียบเท่ากับโซลูชันเฉพาะชนิดตัวเลขที่ให้คุณสร้างอัลกอริธึมตัวเลขทั่วไปที่มีประสิทธิภาพ

นี่คือตัวอย่างของการใช้รหัส

public static T Sum(T[] items)
{
    T sum = Number.Zero<T>();
    foreach (T item in items)
    {
        sum = Number.Add(sum, item);
    }
    return sum;
}
public static T SumAlt(T[] items)
{
    // implicit conversion to Number<T>
    Number<T> sum = Number.Zero<T>();
    foreach (T item in items)
    {
        // operator support
        sum += item;
    }
    // implicit conversion to T
    return sum;
}

1

จุดประสงค์ของการออกกำลังกายคืออะไร?

ตามที่ผู้คนชี้ให้เห็นแล้วคุณอาจมีฟังก์ชั่นที่ไม่ใช่แบบทั่วไปที่ใช้ไอเท็มที่ใหญ่ที่สุดและคอมไพเลอร์จะแปลง ints ขนาดเล็กให้คุณโดยอัตโนมัติ

static bool IntegerFunction(Int64 value) { }

หากฟังก์ชั่นของคุณอยู่บนเส้นทางที่มีความสำคัญต่อประสิทธิภาพ (ไม่น่าจะเกิดขึ้นมากกับ IMO) คุณสามารถระบุโอเวอร์โหลดสำหรับฟังก์ชั่นที่จำเป็นทั้งหมดได้

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }

1
ฉันทำงานกับวิธีเชิงตัวเลขอย่างมาก บางครั้งฉันต้องการจำนวนเต็มและบางครั้งฉันต้องการจุดลอยตัว ทั้งสองรุ่นมี 64 บิตซึ่งเหมาะที่สุดสำหรับการประมวลผลความเร็ว การแปลงระหว่างสิ่งเหล่านี้เป็นความคิดที่แย่มากเพราะมีการสูญเสียในแต่ละทาง ในขณะที่ฉันมักจะใช้คู่เป็นบางครั้งฉันพบว่าการใช้จำนวนเต็มดีกว่าเพราะพวกเขาใช้ที่อื่นได้อย่างไร แต่มันจะดีมากเมื่อฉันเขียนอัลกอริทึมที่จะทำมันครั้งเดียวและปล่อยให้การตัดสินใจประเภทตามข้อกำหนดของอินสแตนซ์
VoteCoffee

1

ฉันจะใช้แบบทั่วไปที่คุณสามารถจัดการกับภายนอก ...

/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
    T NewObject = System.Activator.CreateInstance<T>();

    foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
        NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);

    return NewObject;
}

1

ข้อ จำกัด นี้ส่งผลต่อฉันเมื่อฉันพยายามโอเวอร์โหลดโอเปอเรเตอร์สำหรับประเภททั่วไป เนื่องจากไม่มีข้อ จำกัด "เข้ามา" และด้วยเหตุผลอื่น ๆ ที่ทำให้คนดีใน stackoverflow มีความสุขที่จะให้การดำเนินการไม่สามารถกำหนดประเภททั่วไปได้

ฉันต้องการบางสิ่งบางอย่าง

public struct Foo<T>
{
    public T Value{ get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + RHS.Value; };
    }
}

ฉันได้แก้ไขปัญหานี้โดยใช้การพิมพ์รันไทม์. net4 แบบไดนามิก

public struct Foo<T>
{
    public T Value { get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + (dynamic)RHS.Value };
    }
}

สองสิ่งเกี่ยวกับการใช้dynamicคือ

  1. ประสิทธิภาพ. ประเภทค่าทั้งหมดจะได้รับกล่อง
  2. ข้อผิดพลาดรันไทม์ คุณ "เอาชนะ" คอมไพเลอร์ แต่สูญเสียความปลอดภัยของประเภท หากประเภททั่วไปไม่มีตัวดำเนินการที่กำหนดไว้จะมีข้อยกเว้นเกิดขึ้นระหว่างการดำเนินการ

1

ชนิดดั้งเดิมของ. NET ตัวเลขจะไม่ใช้อินเทอร์เฟซทั่วไปที่จะอนุญาตให้ใช้สำหรับการคำนวณ มันจะเป็นไปได้ที่จะกำหนดอินเตอร์เฟซของคุณเอง (เช่นISignedWholeNumber) ซึ่งจะดำเนินการดังกล่าวกำหนดโครงสร้างที่มีซิงเกิ้ลInt16, Int32ฯลฯ และใช้อินเตอร์เฟซผู้ที่แล้วมีวิธีการที่รับประเภททั่วไปที่ จำกัดISignedWholeNumberแต่มีการแปลงค่าตัวเลข ประเภทโครงสร้างของคุณน่าจะเป็นสิ่งที่สร้างความรำคาญ

วิธีการเลือกที่จะกำหนดระดับคงที่Int64Converter<T>มีคุณสมบัติคงที่bool Available {get;};และผู้แทนคงที่สำหรับInt64 GetInt64(T value), ,T FromInt64(Int64 value) bool TryStoreInt64(Int64 value, ref T dest)ตัวสร้างชั้นสามารถใช้จะกำหนดค่าตายตัวที่จะได้รับมอบหมายภาระชนิดที่รู้จักกันและอาจใช้การสะท้อนในการทดสอบว่าประเภทTวิธีการดำเนินการที่มีชื่อและลายเซ็น (ในกรณีของบางอย่างเช่น struct ซึ่งประกอบด้วยต์Int64และแสดงถึงจำนวน แต่มีToString()วิธีการที่กำหนดเอง) วิธีการนี้จะสูญเสียข้อดีที่เกี่ยวข้องกับการตรวจสอบประเภทเวลารวบรวม แต่ยังคงสามารถจัดการเพื่อหลีกเลี่ยงการดำเนินการชกมวยและแต่ละประเภทจะต้อง "ตรวจสอบ" เพียงครั้งเดียว หลังจากนั้นการดำเนินการที่เกี่ยวข้องกับประเภทนั้นจะถูกแทนที่ด้วยการมอบหมายผู้แทน


@KenKin: IConvertible ให้วิธีการที่จำนวนเต็มใด ๆ ที่สามารถเพิ่มไปยังประเภทจำนวนเต็มอื่นที่จะให้ผลผลิตเช่นInt64ผล แต่ไม่ได้ให้วิธีการที่เช่นจำนวนเต็มชนิดโดยพลการสามารถเพิ่มขึ้นเพื่อให้จำนวนเต็มประเภทเดียวกันอีก
supercat

1

ฉันมีสถานการณ์ที่คล้ายกันซึ่งฉันต้องจัดการกับชนิดและสตริงตัวเลข ดูเหมือนจะเป็นส่วนผสมที่แปลกประหลาด แต่คุณไป

อีกครั้งเช่นเดียวกับหลาย ๆ คนที่ฉันดูข้อ จำกัด และมีอินเทอร์เฟซมากมายที่ต้องสนับสนุน อย่างไรก็ตามก) มันไม่ได้เป็นกันน้ำ 100% และข) ใครก็ตามที่ดูรายการใหม่ของข้อ จำกัด ที่ยาวนานจะสับสนทันที

ดังนั้นวิธีการของฉันคือการทำให้ตรรกะทั้งหมดของฉันเป็นวิธีการทั่วไปโดยไม่มีข้อ จำกัด แต่เพื่อทำให้วิธีการทั่วไปนั้นเป็นส่วนตัว จากนั้นฉันก็เปิดเผยด้วยวิธีสาธารณะหนึ่งในการจัดการประเภทที่ฉันต้องการที่จะจัดการ - ในใจของฉันรหัสสะอาดและชัดเจนเช่น

public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);

private static string DoSomethingHelper<T>(this T input, ....)
{
    // complex logic
}

0

หากสิ่งที่คุณต้องการคือการใช้ประเภทหนึ่งที่เป็นตัวเลขคุณอาจพิจารณาการสร้างสิ่งที่คล้ายกับนามแฝงใน C ++ usingด้วย

ดังนั้นแทนที่จะมีคนสามัญมาก ๆ

T ComputeSomething<T>(T value1, T value2) where T : INumeric { ... }

คุณสามารถมี

using MyNumType = System.Double;
T ComputeSomething<MyNumType>(MyNumType value1, MyNumType value2) { ... }

ซึ่งอาจทำให้คุณสามารถไปจากหรือdoubleไปที่intอื่นได้ง่ายหากจำเป็น แต่คุณจะไม่สามารถใช้ComputeSomethingกับdoubleและintในโปรแกรมเดียวกันได้

แต่ทำไมไม่เปลี่ยนทั้งหมดdoubleเป็นint? เพราะวิธีการของคุณอาจต้องการที่จะใช้doubleไม่ว่าจะใส่เป็นหรือdouble intนามแฝงช่วยให้คุณทราบว่าตัวแปรใดที่ใช้ชนิดไดนามิก


0

หัวข้อเก่า แต่สำหรับผู้อ่านในอนาคต:

คุณลักษณะนี้เกี่ยวข้องอย่างแน่นแฟ้นDiscriminated Unionsซึ่งไม่ได้นำมาใช้ใน C # จนถึงขณะนี้ ฉันพบปัญหาที่นี่:

https://github.com/dotnet/csharplang/issues/113

ปัญหานี้ยังคงเปิดอยู่และมีการวางแผนคุณสมบัติสำหรับ C# 10

ดังนั้นเรายังต้องรออีกเล็กน้อย แต่หลังจากปล่อยคุณสามารถทำได้ด้วยวิธีนี้:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...

-11

ฉันคิดว่าคุณกำลังเข้าใจผิดทั่วไป หากการดำเนินการที่คุณพยายามทำนั้นดีสำหรับชนิดข้อมูลเฉพาะเท่านั้นดังนั้นคุณจะไม่ทำอะไร "ทั่วไป"

นอกจากนี้เนื่องจากคุณเพียงต้องการอนุญาตให้ฟังก์ชันทำงานกับชนิดข้อมูล int เท่านั้นดังนั้นคุณไม่ควรใช้ฟังก์ชันแยกต่างหากสำหรับขนาดเฉพาะแต่ละรายการ เพียงแค่ใช้พารามิเตอร์ในประเภทที่เฉพาะเจาะจงที่ใหญ่ที่สุดจะช่วยให้โปรแกรมสามารถถ่ายทอดข้อมูลขนาดเล็กลงไปโดยอัตโนมัติ (เช่นผ่าน Int16 จะแปลงเป็น Int64 โดยอัตโนมัติเมื่อโทร)

หากคุณกำลังทำงานที่แตกต่างกันตามขนาดที่แท้จริงของ int ที่ถูกส่งผ่านไปยังฟังก์ชั่นจากนั้นฉันคิดว่าคุณควรพิจารณาอย่างจริงจังอีกครั้งแม้จะพยายามทำสิ่งที่คุณทำอยู่ หากคุณต้องใช้ภาษาหลอกคุณควรคิดให้ดีขึ้นเกี่ยวกับสิ่งที่คุณพยายามจะทำมากกว่าที่จะทำในสิ่งที่คุณต้องการ

ไม่สามารถใช้พารามิเตอร์ประเภทวัตถุได้ทั้งหมดจากนั้นคุณจะต้องตรวจสอบประเภทของพารามิเตอร์และดำเนินการตามความเหมาะสมหรือส่งข้อยกเว้น


10
พิจารณาฮิสโตแกรมระดับ <T> มันสมเหตุสมผลที่จะปล่อยให้มันใช้พารามิเตอร์ทั่วไปดังนั้นคอมไพเลอร์สามารถปรับให้เหมาะสมสำหรับไบต์, ints, คู่, ทศนิยม, BigInt, ... แต่ในเวลาเดียวกันคุณต้องป้องกันไม่ให้คุณสามารถสร้าง, พูด, Histogram <Hashset > เพราะ - พูดกับ Tron - มันไม่คำนวณ (ตัวอักษร :))
sunside

15
คุณเป็นคนที่เข้าใจผิดเกี่ยวกับข้อมูลทั่วไป metaprogramming ไม่ได้เป็นเพียงการดำเนินงานในค่าที่อาจเป็นชนิดที่เป็นไปได้ใด ๆก็สำหรับการดำเนินงานเกี่ยวกับประเภทต่างๆที่เหมาะสมกับข้อ จำกัด
Jim Balter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.