ข้อ จำกัด ประเภทหลายวิธีทั่วไป (OR)


136

เมื่ออ่านสิ่งนี้ฉันได้เรียนรู้ว่าเป็นไปได้ที่จะอนุญาตให้วิธีการยอมรับพารามิเตอร์หลายประเภทโดยทำให้เป็นวิธีการทั่วไป ในตัวอย่างรหัสต่อไปนี้ใช้กับข้อ จำกัด ประเภทเพื่อให้แน่ใจว่า "U" เป็นIEnumerable<T>ไฟล์.

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

ฉันพบรหัสเพิ่มเติมที่อนุญาตให้เพิ่มข้อ จำกัด หลายประเภทเช่น:

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

แต่รหัสนี้จะปรากฏขึ้นเพื่อบังคับว่าargต้องมีทั้งชนิดของและParentClass ChildClassสิ่งที่ฉันต้องการทำคือบอกว่า arg อาจเป็นประเภทหนึ่งParentClass หรือ ChildClassในลักษณะต่อไปนี้:

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

ความช่วยเหลือของคุณได้รับการชื่นชมเช่นเคย!


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

@Damien_The_Unbeliever ไม่แน่ใจว่าคุณหมายถึงอะไรในร่างกาย? เว้นแต่คุณหมายถึงการอนุญาตประเภทใด ๆ และการตรวจสอบด้วยตนเองในเนื้อหา ... และในโค้ดจริงที่ฉันต้องการเขียน (ข้อมูลโค้ดสุดท้าย) ฉันต้องการที่จะสามารถส่งผ่านสตริงหรือข้อยกเว้นได้ดังนั้นจึงไม่มีคลาส ความสัมพันธ์ที่นั่น (ยกเว้นระบบวัตถุที่ฉันจินตนาการ)
Mansfield

1
โปรดทราบว่าไม่มีประโยชน์ในการเขียนwhere T : stringเช่นเดียวstringกับคลาสปิดผนึก สิ่งเดียวที่มีประโยชน์ที่คุณทำได้คือการกำหนดโอเวอร์โหลดstringและT : Exceptionตามที่ @ Botz3000 อธิบายไว้ในคำตอบด้านล่าง
Mattias Buelens

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

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

คำตอบ:


70

ที่เป็นไปไม่ได้. อย่างไรก็ตามคุณสามารถกำหนดโอเวอร์โหลดสำหรับประเภทเฉพาะ:

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

หากสิ่งเหล่านี้เป็นส่วนหนึ่งของคลาสทั่วไปพวกเขาจะถูกเลือกมากกว่าเวอร์ชันทั่วไปของวิธีการ


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

3
@Mansfield ฉันไม่รู้เหตุผลที่แน่นอน แต่ฉันคิดว่าคุณจะไม่สามารถใช้พารามิเตอร์ทั่วไปในรูปแบบที่มีความหมายได้อีกต่อไป ภายในชั้นเรียนพวกเขาจะต้องถูกปฏิบัติเหมือนวัตถุหากได้รับอนุญาตให้เป็นประเภทที่แตกต่างกันอย่างสิ้นเชิง นั่นหมายความว่าคุณสามารถละเว้นพารามิเตอร์ทั่วไปและให้โอเวอร์โหลดได้
Botz3000

3
@ แมนส์ฟิลด์เป็นเพราะorความสัมพันธ์ทำให้สิ่งต่างๆเป็นประโยชน์ คุณจะต้องทำการไตร่ตรองเพื่อหาสิ่งที่ต้องทำและทั้งหมดนั้น (ยัย!)
Chris Pfohl

29

คำตอบของ Botz ถูกต้อง 100% นี่คือคำอธิบายสั้น ๆ :

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

ถ้าคุณให้วัตถุที่รู้วิธีทำชุดของสิ่งที่ Type T รู้วิธีทำฉันสามารถส่ง 'a': ค่าส่งคืนของประเภทที่ฉันประกาศหรือ 'b': พฤติกรรมบางอย่างที่ใช้ ประเภทนั้น.

หากคุณลองและให้มากกว่าหนึ่งประเภทในแต่ละครั้ง (โดยมีหรือ) หรือพยายามให้มันส่งคืนค่าที่อาจมีมากกว่าหนึ่งประเภทที่สัญญาจะคลุมเครือ:

ถ้าคุณให้วัตถุที่รู้วิธีกระโดดเชือกหรือรู้วิธีคำนวณค่า pi เป็นหลัก 15 ฉันจะส่งคืนวัตถุที่ตกปลาได้หรืออาจจะผสมคอนกรีต

ปัญหาคือเมื่อคุณเข้าสู่วิธีการที่คุณไม่รู้ว่าพวกเขาให้IJumpRopeหรือPiFactory. นอกจากนี้เมื่อคุณดำเนินการต่อและใช้วิธีการ (สมมติว่าคุณได้รับมันมาเพื่อรวบรวมอย่างน่าอัศจรรย์) คุณไม่แน่ใจจริงๆว่าคุณมีFisherหรือAbstractConcreteMixer. โดยพื้นฐานแล้วมันทำให้ทุกอย่างสับสนมากขึ้น

วิธีแก้ปัญหาของคุณเป็นหนึ่งในสองความเป็นไปได้:

  1. กำหนดวิธีการมากกว่าหนึ่งวิธีที่กำหนดการเปลี่ยนแปลงพฤติกรรมหรืออะไรก็ตามที่เป็นไปได้ นั่นคือคำตอบของ Botz ในโลกของการเขียนโปรแกรมสิ่งนี้เรียกว่า Overloading the method

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

สิ่งที่คุณเลือกขึ้นอยู่กับว่าตัวเลือกที่ 1 และ 2 จะซับซ้อนเพียงใดและต้องขยายได้อย่างไร

ดังนั้นสำหรับสถานการณ์เฉพาะของคุณฉันจะจินตนาการว่าคุณแค่ดึงข้อความหรือบางอย่างออกจากข้อยกเว้น:

public interface IHasMessage
{
    string GetMessage();
}

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

ตอนนี้สิ่งที่คุณต้องมีคือวิธีการที่เปลี่ยน a stringและa Exceptionเป็น IHasMessage ง่ายมาก.


ขอโทษ @ Botz3000 เพิ่งสังเกตว่าฉันสะกดชื่อคุณผิด
Chris Pfohl

หรือคอมไพลเลอร์อาจคุกคามประเภทที่เป็นประเภทสหภาพภายในฟังก์ชันและมีค่าส่งคืนเป็นประเภทเดียวกับที่ได้รับ TypeScript ทำสิ่งนี้
Alex

@ อเล็กซ์ แต่นั่นไม่ใช่สิ่งที่ C # ทำ
Chris Pfohl

นั่นเป็นเรื่องจริง แต่ก็ทำได้ ฉันอ่านคำตอบนี้แล้วเพราะมันไม่สามารถทำได้ฉันตีความผิดหรือเปล่า?
Alex

1
สิ่งนี้ก็คือข้อ จำกัด พารามิเตอร์ทั่วไปของ C # และชื่อสามัญนั้นค่อนข้างดั้งเดิมเมื่อเทียบกับเทมเพลต C ++ C # ต้องการให้คุณบอกคอมไพเลอร์ล่วงหน้าว่าการดำเนินการใดที่อนุญาตให้ใช้กับประเภททั่วไปได้ วิธีการให้ข้อมูลนั้นคือการเพิ่มข้อ จำกัด ของอินเทอร์เฟซการใช้งาน (โดยที่ T: IDisposable) แต่คุณอาจไม่ต้องการให้ประเภทของคุณใช้อินเทอร์เฟซบางอย่างเพื่อใช้วิธีการทั่วไปหรือคุณอาจต้องการอนุญาตบางประเภทในโค้ดทั่วไปของคุณที่ไม่มีอินเทอร์เฟซทั่วไป เช่น อนุญาตให้มีโครงสร้างหรือสตริงใด ๆ เพื่อให้คุณสามารถเรียก Equals (v1, v2) สำหรับการเปรียบเทียบตามค่า
Vakhtang

9

ถ้า ChildClass หมายถึงมันมาจาก ParentClass คุณสามารถเขียนสิ่งต่อไปนี้เพื่อยอมรับทั้ง ParentClass และ ChildClass

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

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

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;
    }
}

จากนั้นคุณสามารถเขียนฟังก์ชันทั่วไปของคุณเป็น;

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

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

นี่เป็นปัญหาการออกแบบในความเป็นจริง ฟังก์ชันทั่วไปใช้เพื่อดำเนินการที่คล้ายกันโดยใช้โค้ดซ้ำ ทั้งสองอย่างหากคุณกำลังวางแผนที่จะดำเนินการที่แตกต่างกันในเนื้อความวิธีการแยกวิธีเป็นวิธีที่ดีกว่า (IMHO)
daryal

สิ่งที่ฉันกำลังทำคือการเขียนฟังก์ชันบันทึกข้อผิดพลาดแบบธรรมดา ฉันต้องการให้พารามิเตอร์สุดท้ายเป็นสตริงข้อมูลเกี่ยวกับข้อผิดพลาดหรือข้อยกเว้นซึ่งในกรณีนี้ฉันจะบันทึก e.message + e.stacktrace เป็นสตริง
Mansfield

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

1

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

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");
            }
        }
    }
}

ชั้นข้างต้นแสดงถึงชนิดที่สามารถเป็นทั้ง TP หรือ TA คุณสามารถใช้มันได้ (ประเภทอ้างอิงกลับไปที่คำตอบเดิมของฉัน):

// ...
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());

หมายเหตุสำคัญ:

  • คุณจะได้รับข้อผิดพลาดรันไทม์หากคุณไม่ตรวจสอบIsPrimaryก่อน
  • คุณสามารถตรวจสอบใด ๆหรือIsNeither IsPrimaryIsAlternate
  • คุณสามารถเข้าถึงค่าผ่านPrimaryและAlternate
  • มีตัวแปลงโดยนัยระหว่าง TP / TA และอย่างใดอย่างหนึ่งเพื่อให้คุณส่งผ่านค่าหรือที่Eitherใดก็ได้ที่คาดหวัง ถ้าคุณทำผ่านEitherที่TAหรือTPคาดว่า แต่Eitherมีผิดประเภทของค่าที่คุณจะได้รับข้อผิดพลาด runtime

ฉันมักจะใช้สิ่งนี้ในที่ที่ฉันต้องการวิธีการส่งคืนผลลัพธ์หรือข้อผิดพลาด มันทำความสะอาดโค้ดสไตล์นั้นจริงๆ ฉันยังเป็นครั้งคราว ( ไม่ค่อย ) ใช้นี้แทน overloads วิธีการ ตามความเป็นจริงนี่เป็นการทดแทนที่แย่มากสำหรับการโอเวอร์โหลด

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