mboost-dp1

Hvad generer dig mest ved C#?


Gå til bund
Gravatar #1 - Acro
25. jan. 2008 18:42
Denne tråd er til at komme med velbegrundede om "fejl" og "mangler" i programmeringssproget C#. Debatten er altså om selve sproget og skal ikke drejes i retningen af patenteringen af .NET eller hvad man ellers kunne finde på.
Gravatar #2 - Acro
25. jan. 2008 18:44
Jeg starter selv. En af de ting, der generer mig mest af manglende kovarians på returværdier i forbindelse med nedarvning af grænseflader. Hvis vi forudsætter følgende:
public interface ITest
{
object Invoke();
}


Dette kan implementeres gyldigt således:
public class ValidTest : ITest
{
public object Invoke()
{
return new User();
}
}


Derimod kunne man forestille sig, at man gerne ville bruge klassen i andre tilfælde og derfor gerne ville angive returtypen i stedet for at angive denne som et object, hvor du dermed forudsætter implicit eller eksplicit konvertering:
public class InvalidTest : ITest
{
public User Invoke()
{
return new User();
}
}


Ovenstående er dog ikke tilladt med C#. Selvom brugerklassen vil nedarve fra det grundlæggende objekt, er ovenstående umuligt.
Gravatar #3 - Acro
25. jan. 2008 18:48
Jeg fortsætter også selv. En anden ting, der generer mig er manglende mulighed for at overloade returværdier i det hele taget. Hvis vi forudsætter følgende:
public class Test
{
public DateTime Invoke()
{
...
}
}


Her har vi altså en metode, der returnerer en dato. Nu er eksemplet ret primitivt, og der vil typisk være en årsag til at lave det som en metode (f.eks. mulighed for ændringer i instansvariabler ved læsning) - eller en række argumenter, der skal sendes med.

Hvis vi dog betragter følgende:
public class Test
{
public DateTime Invoke()
{
...
}

public int Invoke()
{
...
}
}


Ovenstående er nemlig ikke tilladt, og det er egentlig tåbeligt, eftersom oversætteren fint er i stand til at skelne på overloadede argumenter.
Gravatar #4 - themuss
25. jan. 2008 18:53
overloading er ikke tilladt med c#?

Så man skal lave forskellige metoder og ikke blot bruge parametre til at vælge med? Lyder da ikke særlig hensigtmæssigt?
Gravatar #5 - Acro
25. jan. 2008 18:56
#4 themuss:
Overloading tillades på argumenter, så du kan sagtens lave metoder med forskellige signaturer, men du kan ikke lave en metode med argumenter af samme antal og type, der returnerer to forskellige typer.
Gravatar #6 - zin
25. jan. 2008 19:25
Acro: Overloading af returtyper er ikke tilladt fordi det i sådan et tilfælde normalt ville være mere logisk at skrive en ny metode, som kun kan bruges af en instans af den ene klasse (i fht. interfaces) eller også vil du bruge den samme returtype på alle klasserne - og dermed også på Interfacecet.
I forhold til overloading af metoders returtype inden for klasser.. Hmm. Lav den til InvokeInt() og InvokeDateTime()?
Det ville jeg finde mest logisk - det ville være ret træls at skulle gætte sig til hvad returtypen var.
Gravatar #7 - Acro
25. jan. 2008 21:10
#6 ZiN:
Hvorfor er det så tilladt på argumenter? Der kan man jo lige så lidt skelne imellem GetBySingleGuidArgument og GetBySingleStringArgument. At skelne imellem dem bør være en opgave for dig som udvikler (at have overblik og kende det API, du koder op imod) og for det IDE, du anvender. Navngivningskonventioner ligger typisk også op til at være enig med min holdning. Dog forstår jeg godt pointen, men jeg mener, at funktioner, der grundlæggende gør det samme, selvfølgelig bør være den samme.

Det bør netop være afhængigt af kontekst, hvad der sker. Hvis du har en metode, der kan tage et argument af typen objekt og et af en nedarvet klasse, så matcher oversætteren jo også selv på, hvilken der skal udføres. Det er vel ikke så forskelligt fra returværdierne. Hvis du prøver at sætte en variabel med returværdien fra en metode, så er det ret let at udføre et typetjek.

Hvis du f.eks. kigger på DbDataReader og nedarvningerne deraf, returnerer "this[...]" de forskellige værdier, hvor du uden et problem kan konvertere dem til den rigtige datatype, men da de returneres som det generelle objekt, bliver man nødsaget til at konvertere dem. Det er da spild af alles tid. Og da en eksplicit konvertering alligevel først fejler på kørselstidspunktet, vil det ikke være nævneværdigt anderledes, hvis oversætteren matcher nogle typer, der også vil fejle.
Gravatar #8 - zin
25. jan. 2008 21:23
#7:
Hvis du laver metoden og returnerer et objekt men vil returnere et andet er det jo netop at du blot kaster objektet til det objekt du gerne vil have, det skal være - f.eks. kunne du i et Interface kaldet Vehicle have underklasser der hver skulle returnere et array af Vehicles de indeholdte (lad os sige de har en statisk container af sig selv for sjov skyld) - metoden kunne så ligge i Vehicle interfacet med returtype Vehicle - men i tilfældet af, at du vil bruge den som en underklasse af Vehicle kan du jo bare caste den - så længe du ved, hvad du laver, som du selv opfordrer til at man bør - så vil der ikke ske nogle fejl - fejl som ellers nemt vil kunne foregå pga. ovenstående grund.
Lad os sige at Vehicle har en metode der returnerer antal hjul. Fordi designeren af klassen var en torsk gav han den return værdien byte, der maximalt kan indeholde 255. Der er i mellemtiden kommet en køretøjsklasse der kan returnere flere antal hjul med samme metode. Hvordan vil du, som programmør, kunne garantere at når der kaldes metoden fra en instans af Vehicle, at den returnerer Byte og ikke int eller hvad der nu ellers er brugt af returtype i den nye klasse?
Det kan du ikke. :-(
Gravatar #9 - arne_v
25. jan. 2008 22:27
#3

Retur værdien er ikke en del af en metodes signatur.

I den slag sprog som C# tilhører udregner man højre side inden man overhovedet kigger på venstre side.

Det er ret praktisk da det tillader syntax som:

foo.bar(t.Invoke);

og

t.Invoke();

og

s = "bla" + t.Invoke() + "bla";

Hvis man tillod dit syntax forslag, så ville ovenstående ikke være muligt.
Gravatar #10 - Acro
25. jan. 2008 23:30
#9 arne_v:
Det er nu ikke helt rigtigt, at signaturen er uafhængig af returtypen. Hvis du kigger på [url=http:/www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf]Common Language Infrastructure (CLI)[/url], står der udtrykkeligt:
side 31, punkt 8.6.1.5 skrev:
A method signatures is composed of
[list]
[li]a calling convention,[/li]
[li]the number of generic parameters, if the method is generic,[/li]
[li]a list of zero or more parameter signatures—one for each parameter of the method—and,[/li]
[li]a type signature for the result value, if one is produced.[/li]
[/list]


Men om returværdien er en del af signaturen er jo for så vidt irrelevant. Min pointe er jo, at det er en mangel. Du kan sikkert forsvare mange mangler med udgangspunkt i, hvordan tingene er lavet, men derfor kan det jo stadig være interessant, om tingene var lavet anderledes. Det forbydes vist at overloade på returværdier i CLS (jeg er dog ikke sikker), men det er dog muligt i CIL. boo kan så vidt jeg ved også gøre det, og F# kan vel egentlig også. Om det er forbudt i CLS ændrer dog stadig ikke på, at jeg mener, det er en dårlig beslutning.

Vi er forhåbentlig enige om, at oversætteren tjekker typerne, fordi den vil brokke sig over mangel på implicit konvertering, hvis du har en strengvariabel, du prøver at gemme et tal i. Når dette er tilfældet, kan du selvfølgelig også understøtte mit ønske uden de store problemer. Det er rigtigt, at det vil give problemer i forhold til dit sidste eksempel (de to første og mest relevante vil virke uændret), og det sidste er alligevel så dårlig kode, at det ikke er andet end en fordel at forhindre.

Desuden kan man jo sagtens lave kode som dit sidste eksempel, hvis den pågældende metode kun har en returværdi (eller flere returværdier, hvor mindst en er en streng, da denne så vil have forrang). Men sammensætning af strenge bør jo foretages på en anden måde - navnligt hvis de sammensættes af flere typer. Understøttelsen sætter måske nogle større krav til oversætteren, men det er på ingen måde umuligt, og jeg synes, at det er en væsentlig svaghed i forhold til, at man nu har f.eks. DbDataReader, hvor den primære samling returnerer objekter, man selv har ansvar for at konvertere.
Gravatar #11 - Acro
26. jan. 2008 00:02
I samme dokument, jeg linkede til før (nu endda med http://), står der:

side 53 skrev:
CLS Rule 38: Properties and methods can be overloaded based only on the number and types of their parameters, except the conversion operators named op_Implicit and op_Explicit, which can also be overloaded based on their return type.


Det bekræfter jo så også, at typetjekkeren rent faktisk er i stand til at parre venstre- og højresider, og at den manglende overload på returtyper formentlig snarere skyldes kompatibilitet med resten end et begrundet valg.
Gravatar #12 - arne_v
26. jan. 2008 00:23
#10 & #11

Return typen er en del af signaturen på IL level.

Men ECMA 334 som er C# standarden siger i sektion 8.7.3:

Methods can be overloaded, which means that multiple methods can have the same name so long as they
have unique signatures. The signature of a method consists of the name of the method and the number,
modifiers, and types of its formal parameters, and the number of generic type parameters. The signature of a
method does not include the return type or the names of the formal parameters or type parameters.


Mig bekendt er der ingen sprog der har forsøgt at implementere signatur med retur type i.

Fordi som jeg har forsøgt at vise med mine eksempler ville det have nogle meget negative konsekvenser for hvad man kunne i sproget, som slet ikke opvejer fordelene.
Gravatar #13 - arne_v
26. jan. 2008 00:28
#10

Desuden kan man jo sagtens lave kode som dit sidste eksempel, hvis den pågældende metode kun har en returværdi (eller flere returværdier, hvor mindst en er en streng, da denne så vil have forrang).


Det er jo kun et special tilfælde - du kan sagtens finde eksempler hvor der ikke er en metode der vil have naturlig forrang.

Men sammensætning af strenge bør jo foretages på en anden måde - navnligt hvis de sammensættes af flere typer.


Uanset hvad man bør og ikke bør, så er det en del af sproget, som også skal understøttes i fremtidige versioner.
Gravatar #14 - arne_v
26. jan. 2008 00:41
#11

Nej.

Du kan ikke overloade på retur type fordi man ikke altid kender den type der forventes.

Eksplicit type konvertering har ikke brug for at kende den type der forventes.

Implicit type konvertering skal per definition kende den type der forventes.

Men det betyder altså bare at implicit type konvertering ikke giver mening i nogle situationer.

Programmørerne ville blive grumme kede af det hvis de heller ikke kunne bruge metode kald de steder.
Gravatar #15 - arne_v
26. jan. 2008 00:56
#2

Jeg kan godt se din pointe.

Men jeg tror at det ville gøre det lidt forvirrende at læse koden.

Du kan faktisk lave det du vil ved at bruge eksplicit interface.

Eksempel:

using System;

namespace E
{
public interface I
{
object m();
}
public class C : I
{
object I.m()
{
Console.WriteLine("The explicit version called");
return m();
}
public string m()
{
Console.WriteLine("The real version called");
return "blabla";
}
}
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("First I:");
I general = new C();
general.m();
Console.WriteLine("Now C:");
C specific = new C();
specific.m();
Console.ReadKey();
}
}
}
Gravatar #16 - Acro
26. jan. 2008 01:35
#15 arne_v:
Det er typisk også den løsning, jeg ender med, men jeg synes ikke, at det er hensigtsmæssigt sådan. Vi kan jo tage udgangspunkt i klassen HttpWebRequest, der er en nedarvning af WebRequest. Hvis du kalder dens statiske metode Create, så returnerer den et objekt af typen WebRequest. Det betyder, at du eksplicit skal konvertere dit nye objekt til HttpWebRequest, hvis du ønsker at kalde de egenskaber og metoder, der for det er unikt. Det er da alt andet end smart. Det kunne ganske vist være undgået, som du forklarer, men det er alt andet end en optimal løsning.
Gravatar #17 - arne_v
26. jan. 2008 02:29
#16

I det tilfælde kan det ikke undgåes.

Create ligger i WebRequest og returnerer en HttpWebRequest eller FtpWebRequest eller en WhateverWebRequest afhængig af URL.

Den metode kan slet ikke overrides i sub-klasserne.

Heller ikke selvom du fik dine ændringer igennem.
Gravatar #18 - Acro
26. jan. 2008 02:40
#17 arne_v:
Ja, så kan jeg vel lære at kigge på koden, inden jeg kommer med eksempler. Jeg regner dog med, at du forstår pointen alligevel, selvom eksemplet altså kunne have været bedre.
Gravatar #19 - Saxov
27. jan. 2008 17:07
#2, du kan også bruge generics..

e.g.

public interface ITest<T>
{
T Invoke();
}


og så bruger du det som

public class ValidTest : ITest<User>
{
public User Invoke()
{
return new User();
}
}
Gravatar #20 - Acro
27. jan. 2008 19:27
#19 Saxov:
Nu er det som sagt ikke et problem; jeg bruger den løsning, arne_v allerede har foreslået, men derfor er det alligevel ikke en løsning. Ønsket er at have en generel grænseflade, og ITest<T> er ikke det samme som ITest<E>, så skal de begge implementere en ikke-generisk grænseflade (som IEnumerable<T> kræver implementering af IEnumerable og derfor ultimativt også ender ud med en løsning som den, jeg benytter i mangel af bedre).
Gravatar #21 - illishar
5. feb. 2008 16:43
Personligt irriterer jeg mig mest over at man ikke må defininere static functions o.l. i interfaces:

public interface ITest
{
static object Invoke();
}


Det er ret nemt at se at det kan give nogle ubehaglige situationer hvis man prøver at eksekvere kode fra et interface (e.g. static/virtual), da det ikke har noget kode tilknyttet. Men ofte så kunne man godt tænke sig at interface'ene fungerede som 'strict templates' for hvordan nedarvende objekter skal kodes. Hvilket forpures af de manglende static/ctor mm.
Gravatar #22 - Cyrack
5. feb. 2008 17:23
Multipel nedarvning!
Nogen gange vil jeg give mit halve kongerige og en arm for multipel nedarvning :-)

Og så en let måde at tjekke om to objekter implementerer det samme generiske interface, dog uden at tage højde for den specifikke type (dvs. Foo<Object> og Foo<Bar> kan sammenlignes). Ved faktisk ikke om det kan lade sig gøre. Normalt laver jeg "bare" et superinterface som det generiske interface nedarver fra, omend det er grimt og kun giver code-cruft.
Gravatar #23 - Mr.Weasel
7. feb. 2008 13:58
Nok lidt en smagssag, men jeg så gerne at C# ikke var strongly typed, men sådan er folk så forskellige.
Gravatar #24 - illishar
7. feb. 2008 15:54
#23 Så er det vist VB.NET du søger ;)
Gravatar #25 - Acro
7. feb. 2008 22:23
Gravatar #26 - Cyrack
8. feb. 2008 07:26
Hov, nu skal det heller ikke kun være kritik!
Med .Net 3.(0 eller 5?) fik vi partial metoder, hvilket må siges at være en genistreg til sproget.
På samme måde som man kan dele en klasse op i flere filer, så kan man definere sigaturen på en metode i en fil, og implementere den i en anden, alt sammen i samme klasse.
Gravatar #27 - Mr.Weasel
8. feb. 2008 12:59
#24: VB.Net er også strongly type.

Tror jeg har fundet en ny ting
Hashing er lettere debilt.
Fremfor at have en let og gennemskuelig måde at lave en hashing, så skal man igennem følgende:


SHA1Managed sha1 = new SHA1Managed();
hash = Convert.ToBase64String(sha1.ComputeHash(UTF8Encoding.UTF8.GetBytes(Password)));


Åbenbart fordi

hashlib.sha1(Password).digest()
eller

sha1(Password)

havde været for simpelt.
Gravatar #28 - arne_v
8. feb. 2008 13:33
#27

VB.NET kan gøres strongly typed - det er ikke on default.

Jeg forstår ikke dit eksempel.

SHA1 udregnes af bytes ikke af strings. Når man skal konvertere fra string til bytes så skal man naturligvis angive encoding.

SHA1 returnerer et byte array og hvis man vil have en Base64 repræsentation, så skal man naturligvis konvertere til det.

Naturligvis kan man lave:

SHA1.HashGetBytesFromStringUsingUTF8AndBase64EncodeResult(string)

Men sådan laver man altså ikke et reusable framework.
Gravatar #29 - Mr.Weasel
8. feb. 2008 14:13
#28 Det er jo svært ikke at give dig ret i. Typisk er behovet for let at kunne få en din hash ud som en string bare langt mere anvendeligt og det er oftes en string man har brug for. Jeg har endnu aldrig oplevet af have behov for at få min hash ud som et byte array. Så det forekommer mig underligt at der ikke er en simpel måde at generere en hash som en string.

Hvis andre sprog kan, hvorfor skulle jeg så ikke kunne i C# og hvis det er tilstræækeligt i andre sprog, hvorfor er det så ikke i C#?

Jeg tror måske det falder tilbage på typing tingen hvad der er praktisk.

Jeg vidste faktisk ikke at VB.Net som udgangspunkt er dynamicly typed, jeg er åbenbart bare altid blevet tvunget ind i det mode. Det ændre så ikke på at syntaksen er skrækkelig.
Gravatar #30 - arne_v
8. feb. 2008 14:34
#29

VB.NET er ikke dynamic typed, men er default ikke strong typed.

Uden:

Option Strict

så compiler:

Dim i As Integer
i = "123
Gravatar #31 - arne_v
8. feb. 2008 14:39
#29

Med hensyn til hash API, så ved jeg ikke hvilke sprog du tænker på.

Men i Java er det ligesom i .NET og i alle C implementeringer jeg har set, så er det også det samme.
Gravatar #32 - Acro
8. feb. 2008 15:40
#28 arne_v:
Jeg er både enig og uenig med dig. Jeg kan godt følge pointen i, at et framework skal være begrænset, fordi det ultimativt vil være det, der er lettest at lære og understøtte, men omvendt er det jo ikke sådan, det faktisk forholder sig.

Lad os tage udgangspunkt i StreamWriter. Den kan initialiseres med en streng, der angiver en sti, eller en Stream. Hvis din pointe skulle holde, var der ikke nogen grund til at kunne initialisere den med en streng, når man kunne starte med at initialisere en FileStream og give den som argument.

Der skal være overloads i forhold til de ting, der er hyppigst brugt, og hvor det virkelig giver mening. Problemet er selvfølgelig, at man ikke uden videre kan se, om det er base 64-encodede tegn eller de forskellige bytes konverteres til hex. Det er formentlig også årsagen til, at man lader brugeren gøre det selv, fordi man så undgår misforståelser.

Lidt i samme retning har man jo valgt ikke at have en Directory.Copy-metode, fordi den kan laves på mange måder. Det er altså et designvalg, man har truffet.
Gravatar #33 - illishar
11. feb. 2008 09:35
#29

Jeg har aldrig helt forstået hvorfor der er så mange folk der mener at VB-syntax'en er skrækkelig:


C#

if(false == true)
{
RunMyMethod();
}

for(int i = 0; i < 10; i++)
{
RunMyMethodAgain();
}



VB:

If False = True Then
RunMyMethod()
End if

For i as Integer = 0 To 9
RunMyMethodAgain()
Next



Jeg synes ikke at forskellen er vildt stor. (Og den er temmelig ubetydelig, set i forhold til andre sprog.)
Gravatar #34 - myplacedk
11. feb. 2008 11:57
#33
If False = True Then

Hov - enkelt lighedstegn er ikke en sammenligning. Det der giver ikke den store mening for mig. Hvorfor betyder det pludselig noget andet, bare fordi du er inde i en "if"? Hvad er det nu for en sprog-variant man benytter mellem "if" og then"?

Jaja, hvis man ligefrem skriver en masse kode i betingelsen, så er man ude på et sidespor. Men jeg synes alligevel ikke det er intuitivt at syntaxen er anderledes mellem "if" og "then". Det giver også problemer med nogle lidt mere avancerede konstruktioner, som så ikke kan lade sig gøre.

For i as Integer = 0 To 9

Ja, det er jo meget fint hvis man bare lige skal løbe en talrække igennem. Men en for-løkke kan også bruges til andre ting. I den simple ende: Det er ikke intuitivt for mig, hvordan man får den til at løbe baglæns, springe 2 af gangen osv.
Jeg kan forestille mig en del konstruktioner som skal skrives om til en while-løkke.

Jeg synes ikke at forskellen er vildt stor. (Og den er temmelig ubetydelig, set i forhold til andre sprog.)

Njaæh, man kan jo sammenligne med lisp eller brainfuck

Personligt bryder jeg mig heller ikke om den manglende tegnsætning. Fornuftig indrykning og syntax highlighting hjælper da på det, men stadigvæk.

Og det er jo ikke bare at erstatte "end if" med "}" og den slags, som nogen påstår. Som nævnt er der visse konstruktioner som skal skrives helt om.
Gravatar #35 - Saxov
11. feb. 2008 13:42
myplacedk skrev:
Ja, det er jo meget fint hvis man bare lige skal løbe en talrække igennem. Men en for-løkke kan også bruges til andre ting. I den simple ende: Det er ikke intuitivt for mig, hvordan man får den til at løbe baglæns, springe 2 af gangen osv.
Jeg kan forestille mig en del konstruktioner som skal skrives om til en while-løkke.

Du bruger det optionel parameter.

For i as Integer = 0 To 9
er default fordi man normalt går en frem, som i C# med i++.
Men skal man bruge andet, bruger man bare step som i
For i as Integer = 0 To 9 step 2
for at gå to frem hvergang, eller du kan løbe baglands med fx
For i as Integer = 9 To 0 step -1
Gravatar #36 - Happy-Cheese
20. feb. 2008 19:36
For kort navn...
Gå til top

Opret dig som bruger i dag

Det er gratis, og du binder dig ikke til noget.

Når du er oprettet som bruger, får du adgang til en lang række af sidens andre muligheder, såsom at udforme siden efter eget ønske og deltage i diskussionerne.

Opret Bruger Login