GPT 4 Tentafrågor - Generic supertypes & Typ-parameter-begränsningar Flashcards
Givet följande kod:
interface ISequence<T> { T Next(); // ... andra medlemsdefinitioner ... }
-
Vilket av följande påståenden är korrekt gällande ISequence<T>? 1. ISequence<T> är en öppen typ eftersom T inte är specificerad. 2. ISequence<T> kan direkt instansieras med new ISequence<int>(); 3. ISequence<T> måste ha en metod som accepterar en parameter av typen T.
svar 1
ISequence<T> är en öppen typ eftersom T inte är specificerad. Förklaring av varje påstående: "ISequence<T> är en öppen typ eftersom T inte är specificerad." - Detta påstående är korrekt. I termer av generiska typer i C#, är en "öppen typ" en typ som använder en generisk typ parameter, men har inte specificerat en konkret typ för den parametern. ISequence<T> definierar en sådan generisk typ, men tills en konkret typ ersätter T (till exempel när du skapar en instans av ett objekt som implementerar gränssnittet eller definierar en klass som implementerar gränssnittet), är den en öppen typ.
Generiska Interface och Metodimplementering
public interface ISequence<T> { T Next(); } public class StepSequence : ISequence<int> { private int current; private int step; public StepSequence(int start, int step) { this.current = start; this.step = step; } public int Next() { current += step; return current; } }
Vilket påstående är sant?
- StepSequence implementerar Next() metoden på ett sätt som ökar det nuvarande värdet med “step” och returnerar det nya värdet.
- StepSequence måste implementera en Next() metod som returnerar en generisk typ T.
- Klassen StepSequence kan inte implementera ISequence<T> eftersom T är en generisk parameter.</T>
Svar: Alternativ 1.
Förklaring: StepSequence klassen implementerar ISequence<int> med en konkret typ, nämligen int. Detta innebär att metoden Next() måste returnera en int, vilket den gör. Metoden ökar det nuvarande värdet med step och returnerar resultatet, vilket är i linje med klassens avsedda funktionalitet.
De andra alternativen är inte korrekta eftersom de antingen misstolkar hur generiska typer fungerar (som i alternativ 2) eller antyder en begränsning som inte existerar i C# (som i alternativ 3).</int>
Arv och Typkompatibilitet
public class Cycle<T> : IFiniteSequence<T> { private T[] items; private int index = -1; public Cycle(T[] items) { this.items = items ?? throw new ArgumentNullException(nameof(items)); } public T Next() { index = (index + 1) % items.Length; return items[index]; } // Antag att First() och Last() är implementerade korrekt } var cycle = new Cycle<string>(new string[] { "red", "green", "blue" });
Vilket av följande påståenden är sant?
- cycle är en instans av Cycle<T> där T är en string, och den kan därför lagra en sekvens av strängar.</T>
- cycle kan även lagra objekt av typen int eftersom T är generisk.
- Klassen Cycle<T> behöver inte implementera metoden Next() eftersom den inte är en del av IFiniteSequence<T>.</T></T>
Svar: Alternativ 1.
Förklaring: Cycle<T> är en generisk klass, vilket betyder att den kan anpassas till vilken typ som helst. När vi instansierar Cycle<string>, har vi skapat en cykel som hanterar strängar, vilket är anledningen till att vi kan lagra en sekvens av strängar (som "red", "green", "blue"). T i detta fall är bundet till typen string, vilket innebär att instansen av Cycle inte kan hantera andra typer, såsom int (vilket eliminerar alternativ 2). Vad gäller alternativ 3, så kräver arv från ett gränssnitt att alla dess metoder implementeras, så Next() måste finnas i Cycle<T>.</T></string></T>
Beteende hos Generiska Metoder och Typer
interface ISequence<T> { T Next(); T[] Take(int n); } // Antag att vi har en implementerande klass som definierar metoden Next()
Hur beter sig metoden Take(int n) om den anropas på en instans av en klass som implementerar ISequence<int>?</int>
- Den returnerar en array av int som innehåller de nästa n elementen genererade av Next() metoden.
- Metoden Take(int n) måste överlagras i varje klass som implementerar ISequence<T> eftersom den är generisk.</T>
- Metoden Take(int n) kommer att kasta ett undantag vid körning eftersom den inte kan hantera generiska typer.
Svar: Alternativ 1.
Förklaring: Metoden Take(int n) är designad för att fungera med vilken typ som helst T eftersom den är en del av det generiska gränssnittet ISequence<T>. När det används i en klass som implementerar ISequence<int>, förväntas Take(int n) att returnera en array av int, samla de n nästa elementen som genereras av Next() metoden. Detta är standardbeteendet för den här metoden, som definieras i gränssnittet och skulle vara gemensam för alla implementeringar. Alternativ 2 är felaktigt eftersom det inte finns något krav i C# att överlagrade metoder måste definieras för generiska typer. Alternativ 3 är också felaktigt eftersom det inte finns någon indikation i frågeställningen eller i vanlig användning av generiska att ett undantag skulle uppstå.</int></T>
Generiska gränssnitt och implementering
interface ISequence<T> { T Next(); } class NaturalSequence : ISequence<int> { int current = 0; public int Next() => current++; }
Vilket av följande påståenden är sant angående NaturalSequence?
- NaturalSequence är en generisk klass och kan användas med vilken typ som helst.
- NaturalSequence implementerar det generiska gränssnittet ISequence<T> för alla T.</T>
- NaturalSequence är en icke-generisk klass och implementerar det konstruerade generiska gränssnittet ISequence<int>.</int>
Rätt svar är 3. NaturalSequence är en icke-generisk klass och implementerar det konstruerade generiska gränssnittet ISequence<int>.</int>
Förklaring:
Eftersom NaturalSequence implementerar ISequence<int>, har den fastställt typen för ISequence<T> till int och är därför icke-generisk.</T></int>
Generisk arv
class Pair<T1, T2> { public T1 Item1 { get; init; } public T2 Item2 { get; init; } } class MonoPair<T> : Pair<T, T> { public MonoPair(T item1, T item2) : base(item1, item2) { } }
-
Vilket av följande påståenden är sant angående MonoPair<T>? 1. MonoPair<T> är en subtyp av Pair<T1, T2> där både T1 och T2 är av typen T. 2. MonoPair<T> är en överklass till Pair<T1, T2>. 3. MonoPair<T> är en generisk version av Pair<T1, T2> med ytterligare metoder.
Rätt svar är 1. MonoPair<T> är en subtyp av Pair<T1, T2> där både T1 och T2 är av typen T.</T>
Förklaring:
MonoPair<T> är en generisk klass som ärver från Pair<T1, T2>, och eftersom det används samma typ T för både T1 och T2, innebär det att båda elementen i paret måste vara av samma typ.</T>
Arv från generiska gränssnitt
interface ISequence<T> { T Next(); } interface ICycle<T> : ISequence<T> { void Reset(); }
-
Vilket av följande påståenden stämmer angående ICycle<T>? 1. ICycle<T> är en icke-generisk subtyp av ISequence<char>. 2. ICycle<T> är ett generiskt gränssnitt som utökar ISequence<T> med en ytterligare metod. 3. ICycle<T> kan användas direkt utan att specificera någon typ för T.
Rätt svar är 2. ICycle<T> är ett generiskt gränssnitt som utökar ISequence<T> med en ytterligare metod. Förklaring: ICycle<T> är generiskt eftersom det fortfarande har typparametern T och det lägger till en ytterligare metod (Reset()) utöver de metoder som är definierade i ISequence<T>.
Varför använder vi typbegränsningar i generisk programmering i C#?
a) För att tillåta koden att hantera vilken typ som helst utan begränsningar.
b) För att säkerställa att endast objekt av specifika typer eller med vissa egenskaper används, öka typsäkerheten.
c) För att öka komplexiteten i koden genom att lägga till extra villkor.
Rätt svar: b) För att säkerställa att endast objekt av specifika typer eller med vissa egenskaper används, öka typsäkerheten.
Förklaring:
Typbegränsningar i generiska tillåter utvecklare att specificera mer precisa gränser eller krav på de typer som kan användas som argument för en generisk typ. Detta ökar typsäkerheten eftersom kompilatorn kan upptäcka otillåtna operationer eller typkonverteringar vid kompileringstid, vilket minimerar runtime-fel. Det hjälper också till att säkerställa att vissa metoder eller egenskaper är tillgängliga för objekt av den generiska typen, vilket möjliggör mer robust och tydlig kod.
Vad är fördelarna med att använda separat inventering för olika typer (t.ex., Inventory<Weapon> och Inventory<Map>)? a) Det gör det möjligt att lagra olika typer tillsammans, vilket ökar förvirringen. b) Det ökar typsäkerheten och tillåter användning av specifika metoder och egenskaper hos de lagrade objekten utan typkonvertering. c) Det minskar flexibiliteten eftersom varje inventering bara kan hålla en typ av objekt.
Rätt svar: b) Det ökar typsäkerheten och tillåter användning av specifika metoder och egenskaper hos de lagrade objekten utan typkonvertering. Förklaring: Genom att ha separata inventeringar för varje typ, som Inventory<Weapon> och Inventory<Map>, hanterar vi objekt baserat på deras specifika typ, inte genom deras basklass eller gränssnitt. Detta eliminerar behovet av att göra nedåtpekande (downcasting) eller typkontroller för att anropa typspecifika metoder. Det ökar också typsäkerheten eftersom varje inventering säkerställer att den endast innehåller och hanterar objekt av dess specificerade typ.
I ett scenario där du vill säkerställa att en generisk typ måste ha en parameterlös konstruktor, vilken typ av begränsning skulle du då tillämpa?
a) where T : new()
b) where T : class
c) where T : IItem
Rätt svar: a) where T : new()
Förklaring:
I C# kan vi använda begränsningen new() för att ange att en generisk typ T måste ha en offentlig parameterlös konstruktor. Detta är särskilt användbart när vi behöver skapa instanser av T inom vår generiska klass eller metod, eftersom det säkerställer att vi kan göra det utan att ange parametrar. Detta är inte möjligt med de andra alternativen (class specifierar bara att T måste vara en referenstyp, och IItem skulle bara begränsa T att implementera det gränssnittet).
Varför används typbegränsningar i generiska klasser/metoder?
A. För att begränsa typerna som kan användas som argument, vilket garanterar viss funktionalitet eller egenskaper.
B. För att öka komplexiteten i generiska typer.
C. För att undvika att använda interface och abstrakta klasser.
Rätt svar: A
Förklaring:
Typbegränsningar i generiska klasser och metoder används för att säkerställa att de typer som används som argument uppfyller vissa kriterier eller har vissa egenskaper. Detta gör att metoder och klasser kan utnyttja dessa egenskaper eller metoder, vilket ger större typsäkerhet, förbättrad prestanda och kod som är lättare att förstå och underhålla.
Vad händer om du försöker extrahera ett objekt av typen IItem från Inventory< IItem> och kalla på en metod som inte finns i IItem-interfacet?
A. Metoden kommer att köras som normalt utan problem.
B. Det kommer att ske ett kompileringsfel eftersom objektet behandlas som en instans av IItem.
C. Programmet kommer att krascha vid körningstid.
Rätt svar: B
Förklaring:
Eftersom objektet som extraheras ur Inventory<IItem> är av typen IItem, förväntar sig kompilatorn att alla interaktioner med objektet är begränsade till de deklarationer som finns i IItem-interfacet. Om en metod som inte är definierad i IItem försöks kallas, kan inte kompilatorn lösa metoden och genererar därför ett fel vid kompileringstid.</IItem>
Vilken nyckelord används för att ange en begränsning på en generisk typ i C#?
A. constraint
B. restrict
C. where
Rätt svar: C
Förklaring:
I C# används nyckelordet “where” för att införa begränsningar på generiska typparametrar. Genom att lägga till dessa begränsningar kan du ange mer specifika beteenden eller egenskaper som en generisk typ måste uppfylla, vilket ger större flexibilitet och typsäkerhet i din kod.
Vad är fördelen med att ha separata inventeringar för olika typer (som Weapon och Map) i systemet som beskrivs i labben?
A. Det kräver mindre minne att lagra objekten.
B. Det tillåter typspecifik funktionalitet och beteende utan behov av nedtypning.
C. Det eliminerar behovet av gränssnitt och arv.
Rätt svar: B
Förklaring:
När du har separata inventeringar för olika typer, som Weapon och Map, kan du interagera med dessa objekt och utnyttja deras unika metoder och egenskaper utan att behöva göra nedtypning från en överordnad typ eller gränssnitt. Detta ökar typsäkerheten eftersom du arbetar med den specifika typen direkt och gör koden mer läsbar och underhållbar eftersom den undviker onödig typkontroll och omvandling.
Tänk dig att du har infört en ny typ av objekt, “Key”, som implementerar IItem. Vad måste vara sant om din generiska Inventory-klass för att du ska kunna lägga till “Key”-objekt utan problem?
A. Inventory-klassen måste ha en specifik metod för att hantera “Key”-objekt.
B. “Key” måste vara en underklass till en klass redan hanterad av Inventory.
C. Inventory< T> måste begränsas där T är IItem, så att alla typer som implementerar IItem kan hanteras.
Rätt svar: C
Förklaring:
Inventory-klassen är generisk med en typparameter T, och den använder en begränsning för att säkerställa att endast objekt som implementerar IItem-gränssnittet kan läggas till i inventeringen. Detta innebär att så länge den nya “Key”-typen implementerar IItem, kommer den att kunna hanteras av Inventory-klassen utan att någon ytterligare anpassning eller överbelastning behövs. Det säkerställer också att Inventory-klassen kan fortsätta att fungera som avsett med typsäker lagring och hantering av objekt.