Uma propriedade ou indexador não pode ser passado como um parâmetro out ou ref

86

Estou recebendo o erro acima e não consigo resolvê-lo. Eu pesquisei um pouco, mas não consigo me livrar dele.

Cenário:

Tenho a classe BudgetAllocate, cuja propriedade é orçamento, que é do tipo duplo.

Em meu dataAccessLayer,

Em uma de minhas aulas, estou tentando fazer isso:

double.TryParse(objReader[i].ToString(), out bd.Budget);

O que está gerando este erro:

Propriedade ou indexador não pode ser passado como um parâmetro out ou ref em tempo de compilação.

Eu até tentei isso:

double.TryParse(objReader[i].ToString().Equals(DBNull.Value) ? "" : objReader[i].ToString(), out bd.Budget);

Todo o resto está funcionando bem e as referências entre as camadas estão presentes.

Pratik
fonte
Em bd.Budget, bd é um objeto da classe BudgetAllocate. Desculpe eu esqueci.
Pratik
1
possível duplicata de propriedades
Chris Moschini
1
Possível duplicata de propriedades
Legolas
Acabei de descobrir isso trabalhando com um tipo de usuário que tinha campos definidos que era esperado que eu DataGridpreenchesse a partir de então venho aprendê-lo apenas autos com propriedades. Mudar para propriedades quebrou alguns parâmetros ref que eu estava usando em meus campos. Tem que definir variáveis ​​locais para fazer a análise.
jxramos

Respostas:

37

você não pode usar

double.TryParse(objReader[i].ToString(), out bd.Budget); 

substitua bd.Budget por alguma variável.

double k;
double.TryParse(objReader[i].ToString(), out k); 
dhinesh
fonte
11
por que usar uma variável extra ??
Pratik
6
@pratik Você não pode passar uma propriedade como um parâmetro de saída porque não há garantia de que a propriedade realmente tenha um setter, então você precisa da variável extra.
Matt
23
@ mjd79: Seu raciocínio está incorreto. O compilador sabe se existe um setter ou não. Suponha que haja um setter; deveria ser permitido?
Eric Lippert
21
@dhinesh, acho que o OP está procurando uma resposta de por que ele não pode fazer isso, não apenas o que ele deve fazer. Leia a resposta de Hans Passant e os comentários de Eric Lippert.
slugster
2
@dhinesh O motivo "real" pelo qual ele não pode fazer isso é porque ele está usando C # em vez de VB, que permite isso. Eu sou do mundo VB (obviamente?) E geralmente fico surpreso com as restrições extras que o C # impõe.
SteveCinq
149

Outros deram a você a solução, mas por que isso é necessário: uma propriedade é apenas um açúcar sintático para um método .

Por exemplo, quando você declara uma propriedade chamada Namecom um getter e um setter, o compilador realmente gera métodos chamados get_Name()e set_Name(value). Então, quando você lê e grava nessa propriedade, o compilador converte essas operações em chamadas para os métodos gerados.

Quando você considera isso, fica óbvio por que você não pode passar uma propriedade como um parâmetro de saída - você na verdade estaria passando uma referência a um método , em vez de uma referência a um objeto, uma variável , que é o que um parâmetro de saída espera.

Um caso semelhante existe para indexadores.

Mike Chamberlain
fonte
19
Seu raciocínio está correto até a última parte. O parâmetro out está esperando uma referência a uma variável , não a um objeto .
Eric Lippert
@EricLippert mas uma variável também não é um objeto ou o que estou perdendo?
meJustAndrew
6
@meJustAndrew: Uma variável absolutamente não é um objeto . Uma variável é um local de armazenamento . Um local de armazenamento contém (1) uma referência a um objeto do tipo de referência (ou nulo) ou (2) o valor de um objeto do tipo de valor. Não confunda o recipiente com a coisa nele contida.
Eric Lippert,
6
@meJustAndrew: Considere um objeto, digamos, uma casa. Considere um pedaço de papel que contém o endereço da casa. Considere uma gaveta que contém aquele pedaço de papel. Nem a gaveta nem o papel são a casa .
Eric Lippert,
69

Este é um caso de abstração que vaza. Uma propriedade é na verdade um método, os acessadores get e set para um indexador são compilados para os métodos get_Index () e set_Index. O compilador faz um excelente trabalho escondendo esse fato, ele traduz automaticamente uma atribuição a uma propriedade para o método set_Xxx () correspondente, por exemplo.

Mas isso vai de barriga para cima quando você passa um parâmetro de método por referência. Isso requer que o compilador JIT passe um ponteiro para a localização da memória do argumento passado. O problema é que, não existe um, atribuir o valor de uma propriedade requer chamar o método setter. O método chamado não pode dizer a diferença entre uma variável passada e uma propriedade passada e, portanto, não pode saber se uma chamada de método é necessária.

Notável é que isso realmente funciona em VB.NET. Por exemplo:

Class Example
    Public Property Prop As Integer

    Public Sub Test(ByRef arg As Integer)
        arg = 42
    End Sub

    Public Sub Run()
        Test(Prop)   '' No problem
    End Sub
End Class

O compilador VB.NET resolve isso gerando automaticamente este código para o método Run, expresso em C #:

int temp = Prop;
Test(ref temp);
Prop = temp;

Essa é a solução alternativa que você também pode usar. Não tenho certeza de por que a equipe C # não usou a mesma abordagem. Possivelmente porque eles não queriam ocultar as chamadas getter e setter potencialmente caras. Ou o comportamento completamente não diagnosticável que você obterá quando o configurador tiver efeitos colaterais que alteram o valor da propriedade, eles desaparecerão após a atribuição. Diferença clássica entre C # e VB.NET, C # é "sem surpresas", VB.NET é "faça funcionar se você puder".

Hans Passant
fonte
15
Você está correto sobre não querer gerar chamadas caras. Uma razão secundária é que a semântica copy-in-copy-out tem semântica diferente da semântica de referência e seria inconsistente ter duas semânticas sutilmente diferentes para passagem de referência. (Dito isso, existem algumas situações raras em que árvores de expressão compiladas copiam na cópia, infelizmente.)
Eric Lippert
2
O que é realmente necessário é uma variedade maior de modos de passagem de parâmetros, para que o compilador possa substituir "copiar dentro / copiar fora" quando apropriado, mas gritar nos casos em que não é.
supercat
9

Coloque o parâmetro out em uma variável local e, em seguida, defina a variável em bd.Budget:

double tempVar = 0.0;

if (double.TryParse(objReader[i].ToString(), out tempVar))
{
    bd.Budget = tempVar;
}

Atualização : Direto do MSDN:

Propriedades não são variáveis ​​e, portanto, não podem ser passadas como parâmetros de saída.

Adam Houldsworth
fonte
1
@E.vanderSpoel Felizmente, retirei o conteúdo e removi o link.
Adam Houldsworth
8

Possivelmente de interesse - você pode escrever o seu próprio:

    //double.TryParse(, out bd.Budget);
    bool result = TryParse(s, value => bd.Budget = value);
}

public bool TryParse(string s, Action<double> setValue)
{
    double value;
    var result =  double.TryParse(s, out value);
    if (result) setValue(value);
    return result;
}
David Hollinshead
fonte
5

Este é um post muito antigo, mas estou emendando o aceito, porque existe uma forma ainda mais conveniente de fazer isso que eu não conhecia.

É chamado de declaração inline e pode ter estado sempre disponível (como em instruções de uso) ou pode ter sido adicionado com C # 6.0 ou C # 7.0 para esses casos, não tenho certeza, mas funciona perfeitamente de qualquer maneira:

Inetad disso

double temp;
double.TryParse(objReader[i].ToString(), out temp);
bd.Budget = temp;

usa isto:

double.TryParse(objReader[i].ToString(), out double temp);
bd.Budget = temp;
DanDan
fonte
2
Eu usaria o return para verificar se a análise foi bem-sucedida em caso de entrada inválida.
MarcelDevG
1

Então o orçamento é uma propriedade, correto?

Em vez disso, primeiro defina-o como uma variável local e, em seguida, defina o valor da propriedade para isso.

double t = 0;
double.TryParse(objReader[i].ToString(), out t); 
bd.Budget = t;
Adriaan Stander
fonte
Obrigado. Mas posso saber por quê?
Pratik
0

Normalmente, quando tento fazer isso, é porque quero definir minha propriedade ou deixá-la com o valor padrão. Com a ajuda desta resposta e dynamictipos, podemos criar facilmente um método de extensão de string para mantê-lo alinhado e simples.

public static dynamic ParseAny(this string text, Type type)
{
     var converter = TypeDescriptor.GetConverter(type);
     if (converter != null && converter.IsValid(text))
          return converter.ConvertFromString(text);
     else
          return Activator.CreateInstance(type);
}

Use assim;

bd.Budget = objReader[i].ToString().ParseAny(typeof(double));

// Examples
int intTest = "1234".ParseAny(typeof(int)); // Result: 1234
double doubleTest = "12.34".ParseAny(typeof(double)); // Result: 12.34
decimal pass = "12.34".ParseAny(typeof(decimal)); // Result: 12.34
decimal fail = "abc".ParseAny(typeof(decimal)); // Result: 0
string nullStr = null;
decimal failedNull = nullStr.ParseAny(typeof(decimal)); // Result: 0

Opcional

Em uma nota lateral, se for um, SQLDataReadervocê também pode fazer uso de GetSafeStringextensão (ões) para evitar exceções nulas do leitor.

public static string GetSafeString(this SqlDataReader reader, int colIndex)
{
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

public static string GetSafeString(this SqlDataReader reader, string colName)
{
     int colIndex = reader.GetOrdinal(colName);
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

Use assim;

bd.Budget = objReader.GetSafeString(i).ParseAny(typeof(double));
bd.Budget = objReader.GetSafeString("ColumnName").ParseAny(typeof(double));
Clamchoda
fonte