Tentaliknande frågor från Föreläsningar Flashcards
- Konceptet: generic type
class Pair<T> { public T Item1 { get; set; } public T Item2 { get; set; } public Pair(T item1, T item2) { Item1 = item1; Item2 = item2; } }
Vilka rader kompilerar?
1. var p1 = new Pair<int, bool>(1, true); 2. Pair<T> p2 = new Pair<T>(); 3. Pair p3 = new Pair(10, 20); 4. Pair<bool> p4 = new Pair<bool>(true, true);
Rättsvar: 4.
Pair<bool> p4 = new Pair<bool>(true, true); I den här raden försöker du skapa en instans av Pair<bool> korrekt genom att ange typparametern som bool. Denna rad kommer att kompilera korrekt om du vill skapa en Pair<bool>-instans.
- Koncept: Generics och Typfel
class Pair<T> { public T Item1 { get; set; } public T Item2 { get; set; } } var pair = new Pair<int, bool> { Item1 = 42, Item2 = true }; int second = pair.Item2;
Vilket alternativ är korrekt?
1. Kompilerar och körs.
2. Kompilerar men kastar exception. .
3. Kompilerar inte.
Rättsvar: Alternativ 3 är korrekt.
- Motivering:
När du försöker använda Pair<int, bool>, måste du ange två typer, men klassen Pair är konstruerad för att ha en och samma typ. Därför kommer det att orsaka ett kompileringsfel.
- Koncept: Generic interface
interface ISequence<T> { T Next(); } class NaturalSequence : ISequence<int> { int current = 0; public int Next() => current++; }
- Vilka rader kompilerar? 1. NaturalSequence seq1 = new NaturalSequence(); 2. ISequence<int> seq2 = new NaturalSequence(); 3. ISequence<char> seq3 = new NaturalSequence();
- Rättsvar: 1 och 2 kompilerar.
Motivering:
- Kompilerar:
Här skapar du en instans av NaturalSequence och tilldelar den till seq1, vilket är en korrekt typmatchning.
Eftersom NaturalSequence implementerar ISequence<int>, kan du tilldela en instans av NaturalSequence till en variabel deklarerad som NaturalSequence. Detta är känt som en implicit typkonvertering.</int>
- Kompilerar
Här skapar du också en instans av NaturalSequence och tilldelar den till seq2, som är deklarerad som en ISequence<int>.
Eftersom NaturalSequence implementerar ISequence<int>, kan du tilldela den till en variabel som har den typen. Detta möjliggör att du använder polymorfism för att hantera NaturalSequence som en ISequence<int>.</int></int></int>
- Kompilerar inte:
Även om NaturalSequence implementerar ISequence<int>, kan du inte tilldela en instans av NaturalSequence till en variabel av en annan generisk typparameter, som ISequence<char>.
Generiska typer är typsäkra, och de måste matcha exakt. I det här fallet måste typen matcha ISequence<int> om du vill tilldela en instans av NaturalSequence till det.</int></char></int>
Koncept: Generic interface
interface ISequence<T> { T Next(); } class Alphabet : ISequence<char> { int i = -1; char[] letters = { 'A', 'B', 'C', /*...*/ 'Z' }; public char Next() => letters[i = (i + 1) % letters.Length]; }
- Vilka rader kompilerar?
1. Alphabet seq1 = new Alphabet(); 2. ISequence<int> seq2 = new Alphabet(); 3. ISequence<char> seq3 = new Alphabet();
- Rättsvar: 1 och 3 kompilerar.
Motivering:
- Kompilerar:
Eftersom du skapar en instans av Alphabet och tilldelar den till en variabel av samma typ, Alphabet.
- Kompilerar inte:
Även om Alphabet implementerar ISequence< char >, försöker du tilldela en instans av Alphabet till en variabel av typen ISequence< int >. Generiska typer måste matcha exakt, och ISequence< int > och ISequence< char > är inte kompatibla eftersom de har olika generiska typparametrar.
- Kompilerar:
Denna rad kommer att kompilera eftersom Alphabet implementerar ISequence< char >, och du tilldelar instansen av Alphabet till en variabel av samma typ, ISequence< char >.
Koncept: Varians
class Fruit { } class Apple : Fruit { } Fruit fruit = new Apple(); IEnumerable<Fruit> fruits = new List<Apple>();
Är en lista av äpplen en lista av frukt?
Ja, när det kommer till IEnumerable och andra kovarianta gränssnitt, kan en lista av äpplen (List< Apple>) behandlas som en lista av frukt (IEnumerable< Fruit>) om Apple är en subtyp av Fruit.
Är detta ett öppet eller stängt arv av en generic interface?
interface ISequence<T> { T Next(); } interface ICharSequence : ISequence<char> { void SetUpperCase(); // Changes to uppercase. void SetLowerCase(); // Changes to lowercase. }
Rättsvar: Stängd.
Motivering:
I det här fallet har du stängt den generiska typen T till char genom att använda ISequence<char> som den generiska typen. Nu är ICharSequence specifikt för tecken (char) och inte längre generiskt.
Är detta ett öppet eller stängt arv av en generic interface?
iinterface ISequence<T> { T Next(); } interface ICycle<T> : ISequence<T> { void Reset(); }
Rättsvar: Öppet
Har finns ingen typbegränsning som t.ex. char eller int.
Koncept: Öppen constructed generic interface.
class Pair<T1, T2> { public T1 Item1 { get; init; } public T2 Item2 { get; init; } public Pair(T1 item1, T2 item2) { Item1 = item1; Item2 = item2; } } class MonoPair<T> : Pair<T, T> { public MonoPair(T item1, T item2) : base(item1, item2) { } }
Vilka rader kompilerar?
1. MonoPair<int> pair1 = new MonoPair<int>(2, 3); 2. Pair<char, char> pair2 = new MonoPair<char>('A', 'B'); 3. Pair<string> pair3 = new MonoPair<string>("hello", "world");
Rättsvar: 1 och 2 kompilerar.
- Kompilerar:
Du skapar en instans av MonoPair< int > och tilldelar den till en variabel av samma typ, MonoPair< int >, vilket är en korrekt användning av den generiska typen.
- Kompilerar:
Du skapar en instans av MonoPair< char > och tilldelar den till en variabel av typen Pair< char, char >. Eftersom MonoPair< T > ärver från Pair< T, T >, kan du använda en instans av MonoPair< T > som en instans av Pair< T1, T2 >.
- Kompilerar inte:
Du försöker skapa en instans av MonoPair< string > och tilldela den till en variabel av typen Pair< string >. Eftersom MonoPair< T > har två generiska typparametrar och Pair< string > har bara en, är de inte kompatibla, och denna rad kommer att orsaka ett kompileringsfel.
Anta att:
Apple : Fruit Fruit : Edible T är kovariant i I<T> . Vilka påståenden stämmer?
1. I<Apple> : I<Fruit> 2. I<Fruit> : I<Apple> 3. I<Apple> : I<Edible> 4. I<Edible> : I<Fruit>
Rättsvar: 1 och 3 stämmer.
Motivering:
1. Stämmer: <Apple> är en mer specifik typ än <Fruit>, så du kan använda kovarians för att tilldela en instans av <Apple> till <Fruit>. Detta är tillåtet eftersom Apple ärver från Fruit. 2. Stämmer inte: Eftersom kovarianstypning tillåter dig att tilldela en mer specifik typ till en mer generell typ, kan du inte tilldela <Fruit> till <Apple> eftersom det skulle vara kontravariant. Kovarianstypning fungerar endast i riktning från mer specifikt till mer generellt. 3. Stämmer: Eftersom Edible är en överordnad typ till Fruit, kan du använda kovarians för att tilldela en instans av <Apple> till <Edible>. Om T är kovariant i <T>, kan du gå upp i hierarkin av ärftliga typer. 4. Stämmer inte: Eftersom Edible är överordnad Fruit, kan du inte använda kovarians för att tilldela <Edible> till <Fruit>. Kovarianstypning fungerar endast i riktning från mer specifikt till mer generellt.
Koncept: Generic interface med constraint.
public class Inventory<T> where T : IItem { private List<T> items = new List<T>(); private int currentIndex = 0; public string CurrentName => CurrentItem.Name; public T CurrentItem => items[currentIndex]; public void Add(T item) => items.Add(item); public void Next() => currentIndex = (currentIndex + 1) % items.Count; }
Vilka av följande påståenden stämmer om varför detta constraint behövs?
- Säkerhet:
Denna constraint begränsar användningen av generiska typer till de som uppfyller vissa krav, vilket ökar säkerheten i din kod.
- Metodåtkomst:
Genom att kräva att generiska typer implementerar ett gränssnitt (eller har vissa egenskaper/metoder), kan du vara säker på att du kan använda dessa metoder i din generiska klass.
3: Typtillförlitlighet:
Denna constraint hjälper till att säkerställa att generiska typer är av de förväntade typerna eller implementerar de krav som behövs, vilket ökar typtillförlitligheten i din kod.
Rättsvar: Samtliga stämmer.
class Fruit { } class Apple : Fruit { } class AppleJuicer { public virtual void Juice(Apple apple) => Console.WriteLine("Turning apple into juice"); } class FruitJuicer : AppleJuicer { public override void Juice(Fruit fruit) => Console.WriteLine("Turning fruit into juice");
- Vilka påståenden stämmer:
- FruitJuicer är typ-säker.
- AppleJuicer är typ-säker.
- Varken FruitJuicer eller AppleJuicer är typ-säkra.
Rättsvar: 3 stämmer.
Motivering:
I det givna exemplet är varken FruitJuicer eller AppleJuicer typsäkra. För att vara typsäker måste de överskuggade metoderna ha identiska signaturer.
class Player1 { bool has DoubleJumpPowerUp = false; public void MoveRight()=> Console.WriteLine("Moving right."); public void Jump() { if (!hasDoubleJumpPowerUp) Console.WriteLine("Jumping"); else Console.WriteLine("Double jumping"); }
Svara på följande frågor om lösningen ovan som inte implemetarar dependensy injection:
- Varför är det problematiskt att ha hårdkodade variabler som hasDoubleJumpPowerUp i koden?
- Hur påverkar bristen på abstraktion i koden hanteringen av olika typer av power-ups?
- Varför är det en utmaning att underhålla koden när antalet conditionals ökar för att hantera olika typer av power-ups?
- Hårdkodade variabler:
Du har hårdkodat hasDoubleJumpPowerUp som en instansvariabel i Player1. Detta innebär att du skulle behöva ändra koden direkt i klassen varje gång du vill lägga till eller ta bort en ny typ av power-up. Detta är varken skalbart eller underhållbart, särskilt om du har många olika typer av power-ups.
- Brist på abstraktion:
Koden har ingen mekanism för att hantera olika typer av power-ups. Om du vill lägga till fler typer av power-ups i framtiden, måste du sannolikt ändra koden i Player1, vilket bryter mot öppen/sluten principen.
- Många conditionals:
Om du skulle lägga till fler power-ups, skulle du behöva lägga till fler villkor för att hantera varje typ av power-up. Detta kan bli rörigt och svårt att underhålla över tiden.
Anta att:
Apple : Fruit Fruit : Edible T är kontravariant i I< T > .
Vilka påståenden stämmer?
1. I<Apple> : I<Fruit> 2. I<Fruit> : I<Apple> 3. I<Apple> : I<Edible> 4. I<Edible> : I<Fruit>
Svar: 2 och 4
Koncept: Komposition över Arv
abstract class Duck { public virtual string Quack() => "Quack!"; public virtual string Swim() => "Swim!"; } class MallardDuck : Duck { } // Andra metoder relaterade till Mallard... class RedheadDuck : Duck { }
Vad är problemet som uppstår om skulle vilja lägga till en RubberDuck?
class RubberDuck : Duck { public override string Quack() => "Squeak!"; public override string Swim() => "Floats."; }
Problemet som uppstår när du lägger till RubberDuck klassen är att den ärver från Duck, och det finns en konflikt mellan den naturliga beteendet som Duck har definierat och det specifika beteendet som RubberDuck försöker införa.
} string PositiveOrNegative(int number) => number > 0 ? "Positive" : "Negative"; string ToHex(int number) => number.ToString("X"); string FizzBuzz(int number) { if (number % 15 == 0) return "FizzBuzz"; else if (number % 3 == 0) return "Fizz"; else if (number % 5 == 0) return "Buzz"; else return number.ToString(); }
Vad har alla dessa gemensamt?
Du får klura vidare på den ;)
Koncept: funktions delegat
public class Player { JumpBehavior jumpBehavior; public Player(JumpBehavior behavior) => jumpBehavior = behavior; public void SetJumpBehavior (JumpBehavior behavior) => jumpBehavior = behavior; public void Jump() => jumpBehavior(); }
Vilken rad är korrekt?
- player.Jump();
- Player jumpBehavior = new Player(Jumping);
Rättsvar: 1 är rätt svar player.Jump();
Motivering:
Här anropar du metoden Jump() på en befintlig instans av Player. Detta kommer att använda den aktuella jumpBehavior som har satts tidigare och utföra hoppbeteendet som är associerat med den instansen.
Varför använda Funktions delegat?
Eftersom man kan ändra beteendet hos en instans av Player genom att bara byta ut den refererade metoden, vilket ger stor flexibilitet och dynamik.
Koncept: Funktions delegat
delegate int Operation(int value); int Dec (int x) => x - 1; int Add (int x, int y) => x + y; int Inv (int x) => -x;
- Vilka rader Kompilerar?
- Operation op1 = Dec;
- Operation op2 = Add;
- Operation op3 = Inv;
Rättsvar: 1 och 3 kompilerar.
Motivering:
- Kompilerar:
Du tilldelar Dec-metoden till op1, och eftersom metoden Dec har samma signatur som Operation, kan du använda den i en Operation-delegat. - Kompilerar inte:
Även om Add-metoden har samma returtyp som Operation, har den en annan parametertyp (två int-parametrar istället för en). Detta innebär att Add inte kan tilldelas en Operation-delegat direkt. - Kompilerar:
Inv-metoden har samma signatur som Operation med en int-parametrar och en int-returtyp.