Microsoft-ui-xaml: Proposta: as projeções C # de Ponto, Reto e Tamanho não devem fingir ser de precisão dupla

Criado em 1 mar. 2020  ·  59Comentários  ·  Fonte: microsoft/microsoft-ui-xaml

Em C #, parece que Point, Rect e Size têm precisão dupla, pois todas as suas propriedades são duplas. Mas, na verdade, este não é o caso, eles são, na verdade, de precisão única. Isso é uma prática muito confusa e extremamente ruim, na minha opinião. Caso alguém queira verificar isso, tente o seguinte código.

`` `C #
double x = (duplo) float.Epsilon / 4.0;
System.Diagnostics.Debug.WriteLine (x);
Ponto p = novo Ponto (x, 0);
System.Diagnostics.Debug.WriteLine (pX);

It prints

3,50324616081204E-46
0
`` `
(Observe que você pode estar pensando que o número acima é ridiculamente pequeno, mas na prática, ao trabalhar em escalas sensíveis de cerca de 100-1000 para pixels de layout, a precisão dos flutuadores cai para algo mais como 0,0001, eu simplesmente não poderia me incomodar em calcule os valores para o exemplo nesse caso).

A maioria das pessoas não perceberá o fato de que essas estruturas são de precisão simples, o que pode levar a muitos problemas. Por exemplo, um tempo atrás, tentei armazenar coordenadas GPS em uma estrutura Point, pois já estava usando o namespace Windows.Foundation e parecia conveniente. No entanto, descobri que eles perderam a precisão e parece que há séculos para descobrir por quê. Outro exemplo é que escrevi um código para fazer alguns cálculos de geometria coordenada, o que envolveu a adição de uma tolerância para permitir erros aritméticos de ponto flutuante. Tive que fazer essa tolerância muito maior do que esperava e nunca entendi por que na época, mas acontece que era porque eu estava usando pontos que achei serem de precisão dupla, mas na verdade eram únicos.

Eu proponho que as proteções C # de Point, Rect, Size devem ter propriedades que são floats. (Como um aparte, em minha opinião foi um erro usar precisão simples em vez de precisão dupla para propriedades de layout como essas em primeiro lugar, porque os erros são desconfortavelmente grandes, mas suponho que essa decisão tenha sido tomada agora).

area-DotNet bug team-Markup

Comentários muito úteis

@dotMorten , et al - obrigado pelo feedback. Revisamos a mudança das propriedades double para float em Rect, Point e Size. Embora torne a projeção tecnicamente "correta", ela cria impedância demais com outras propriedades e variáveis ​​duplas e cria grandes problemas de compatibilidade. Portanto, decidimos reverter a mudança.

Todos 59 comentários

Em C #, parece que Point, Rect e Size têm precisão dupla, pois todas as suas propriedades são duplas. Mas, na verdade, este não é o caso, eles são, na verdade, de precisão única. Isso é uma prática muito confusa e extremamente ruim, na minha opinião. Caso alguém queira verificar isso, tente o seguinte código.

Eu mais que apoio isso. Levei um bom tempo para descobrir isso por conta própria - já que também presumi que seriam o dobro, apenas para ver que não são. Então, basicamente, qualquer set você fizer é convertido para float.

Acho que o problema é que, ao usar um ponto em XAML, a especificação de um float (também conhecido como único) não é compatível / funciona.

Da documentação :

Se você quiser um DP com um tipo de ponto flutuante, faça-o duplo (duplo no MIDL 3.0). Declarar e implementar um DP do tipo float (Único em MIDL) e, em seguida, definir um valor para esse DP na marcação XAML, resulta no erro Falha ao criar um 'Windows.Foundation.Single' a partir do texto ''.

Então, meu palpite é que, para oferecer suporte ao uso de pontos em XAML, eles os definiram como duplos na camada de abstração.

@stevenbrix isso será resolvido pelo novo trabalho de projeção?

Obrigado por acompanhar isso. Eu só queria salientar que seria uma mudança significativa transformar as propriedades em flutuadores, o que não me incomoda (e obviamente não teria postado o problema se não fosse), mas seria bom adicionar para uma lista documentada de alterações importantes em algum lugar. A menos, é claro, que você planeje fazer com que o objeto subjacente realmente armazene valores de precisão dupla.

isso será resolvido pelo novo trabalho de projeção?

Não tenho certeza, vou anotar isso. Corrigir isso seria uma alteração de interrupção no nível da fonte ou no nível do tempo de execução.

Isso é interessante, a documentação aqui diz que deve ser um float: https://docs.microsoft.com/en-us/uwp/api/windows.foundation.point.x

A motivação para Point ser solteiro era o desempenho. Olhamos em algum lugar ao longo da linha e o single era predominantemente precisão suficiente, e o double causou um impacto mensurável no desempenho, então tentamos fazer as coisas geralmente single internamente. Ao mesmo tempo, há uma diretriz de API (em .Net e Windows) para preferir double, porque linguagens como double melhor do que single (ex var num = 0.0 em C # torna num um duplo).

O resultado dessas motivações opostas é que, para Silverlight e Xaml (WinUI), alguns tipos são únicos internamente, mas são projetados na API pública como duplos. Por exemplo, FrameworkElement.Width é um double na API, mas um float internamente. Por outro lado, MapControl.ZoomLevel , onde a precisão realmente importa, é realmente um duplo internamente.

Quando o tipo Windows Point foi originalmente projetado, ele foi deixado flutuante, e você pode ver isso em C ++. Em C #, porém, ele ainda é projetado como um duplo para compatibilidade. Há uma observação na página de doc do Point sobre isso (mas não na página de doc do campo que stevenbrix vinculou).

(Fyi, há também um tipo PointInt32 que é usado em alguns lugares que é Int32 de baixo para cima.)

@stevenbrix , você vinculou os documentos UWP para mostrar 'float X'. Mas os documentos dotnet são de dupla precisão. Então eu não acho que haja qualquer problema aí, além de ser um pouco confuso mostrar C #, já que a projeção esconde isso.

A projeção existente para Point, Size, Rect é transportada para o namespace Windows.Foundation, em vez de usar os tipos System.Windows de precisão dupla. Dado isso, estou aberto para consertar isso preservando o mapeamento WF, mas tornando os tipos de precisão única. É uma mudança significativa, mas uma oportunidade rara.

@danzil , de acordo com a observação de Xaml precisaria ser alterado para acomodar estruturas de precisão simples?

É uma mudança significativa, mas uma oportunidade rara.

Isso seria incrível, muitos possíveis bugs de truncamento desaparecerão.

Mas os médicos não levam você lá, eles levam você para aquele que o Stevenbrix vinculou. (Por exemplo, comece em PointAnimation e siga o link para Point.)

Estamos trabalhando muito para não tratar o WinUI 3 como uma oportunidade de fazer alterações significativas. Há coisas que precisamos interromper por motivos técnicos ou de tempo, então evitamos qualquer problema de alteração que possa ser evitado. (Por exemplo, isso se torna um erro do compilador: point.X = 4.2; ) Não poderíamos fazer essa alteração em uma versão subsequente?

Sim, os documentos UWP são um pouco confusos como ponto de partida, pois não têm conhecimento do comportamento de projeção C #.

Para C # / WinRT, partimos do pressuposto de que os usuários terão que reconstruir e possivelmente reescrever um pouco (por exemplo, eliminando os helpers de interoperabilidade do winrt). Parece que o WinUI está sendo mais conservador, e esse é um desafio onde os dois se cruzam. Acho que interromper as mudanças será mais difícil quanto mais avançarmos, à medida que mais fontes dependentes se acumulem.

Como desenvolvedor, posso dizer que a migração para o WinUI 3.0 seria o ponto principal em que eu esperaria fazer alterações em meu código, portanto, seria a favor de fazer alterações significativas nessa fase. Eu acharia mais confuso ter que fazer alterações no WinUI 3.1 ou algo assim.

No entanto, eu entendo que você pode ter decidido fazer o mínimo possível de alterações importantes no WinUI 3.0, a fim de incentivar as pessoas a migrar para o WinUI 3.0 (porque você não quer ficar com muitos aplicativos ainda usando os controles no namespace do Windows, caso contrário, talvez você tenha que se esforçar mais para apoiá-los).

No geral, acho que todas as coisas novas que estão surgindo, por exemplo .net 5, WinUI 3, apresentam uma boa oportunidade para fazer algumas alterações importantes como essa no momento em que os desenvolvedores esperam. Se o seu plano é fazer mudanças significativas em uma versão futura, seria bom deixar isso bem claro no roteiro e incluí-lo em todos os recursos visuais que você tiver, para que os desenvolvedores estejam preparados. Por exemplo

  • WinUI 3.0 - alterações mínimas de interrupção, exceto namespace do Windows -> Microsoft.
  • WinUI 3.1 - outras (número limitado de) alterações de interrupção planejadas

Uma coisa que acho que concordamos é que o comportamento atual do Point etc em C # é confuso! (Eu meio que entendo a motivação original, mas não tenho certeza se foi a melhor decisão ...).

Como um aparte, o que provavelmente não será útil de forma alguma neste estágio, os ganhos de desempenho do uso de float em vez de double para pontos ainda existem, visto que a maioria das pessoas agora estará executando dispositivos de 64 bits? Eu sei que existem algumas instruções especiais da CPU que permitem cálculos mais rápidos com floats, mas até que ponto isso é explorado? (Eu também entendo que, eventualmente, as coisas são passadas para Direct2D, que usa flutuadores em geral (e as GPUs são definitivamente mais rápidas com cálculos de precisão única), mas a conversão pode ter acontecido nesta interface.)

Além disso, com relação ao ponto sobre o compilador XAML precisar ser alterado, não sei a resposta para isso, mas postei um problema para ele ao mesmo tempo que publiquei este, caso estivesse relacionado - consulte # 2048.

Esses são erros de API que precisam ser corrigidos, quanto antes melhor. Se você não consertar isso por uma questão de compatibilidade, isso seria um grande sinal de que o próximo WinUI é apenas uma solução provisória. Basicamente, ele estaria morto antes de ser lançado porque já está claro que precisa de outra geração de estrutura XAML para fazer o design certo. Isso não vai exatamente atrair as pessoas para o uso do WinUI.

Pelo que vale a pena, os frameworks de jogos estão indo bem com float e C #, então isso não é nada que não possa ser resolvido. O projeto original de usar fingir duplo e truncamento silencioso é simplesmente errado.

O projeto original de usar fingir duplo e truncamento silencioso é simplesmente errado.

Concordo, eu acho que estes deveriam apenas projetar como eles são implementados sob as tampas (como flutuadores). Se o compilador / tempo de execução precisa ser corrigido para aceitar floats, então, em minha opinião, é isso que devemos fazer. FrameworkElement.Width é double , porém, então este é um daqueles casos em que nossos tipos internos se comportam de maneira diferente dos externos?

Após uma discussão relacionada, a proposta foi apresentada para manter os acessadores de propriedade de precisão dupla, digamos Point.X, e adicionar verificação de limites (ou seja, lançar se o duplo for maior do que um flutuante), mas também adicionar acesso direto e eficiente ao único - lojas de apoio de precisão. A estrutura projetada seria blittable, então cruzar o ABI seria barato.

Então,
Ponto pt;
pt.X = GetDouble ();
pt.x = GetFloat ();

pensamentos?

Eu acho que isso poderia criar problemas de definir o valor para um dobro acidentalmente e os aplicativos começarem a ter que lidar com essa exceção.

Por outro lado, isso certamente aumentaria a consciência para a situação e as pessoas definitivamente saberão quando os valores são imprecisos.

jogue se o dobro for maior que um flutuador

O que exatamente isso significa? Float já inclui infinity então presumo que seja um case especial. Você quer dizer verificar o valor absoluto como as verificações de limites para números inteiros fazem? Isso seria equivalente a um limite superior para o expoente. Não acho que exceder isso aconteça com muita frequência, seria mais útil ter uma verificação de limites inferiores para o expoente, ou seja, quando você chegar perto o suficiente de zero, a conversão para float aumentaria o expoente.

Evento que pode não acontecer muito na prática, o que as pessoas provavelmente estão encontrando é o estouro da mantissa, ou seja, a conversão para o corte flutuante da mantissa, mas você precisava desses bits. Verificar isso de uma maneira sensata provavelmente é impossível porque basicamente qualquer número não divisível por dois terá bits não regulares na área da mantissa cortada. Como o elenco vai saber se isso foi importante?

Não lance uma exceção! Não vejo como isso vai ajudar ninguém. As pessoas não vão perceber que lançar uma exceção é possível e a única coisa que vai fazer é levar à possibilidade de mais exceções não manipuladas (embora deva ser raro de qualquer maneira). Também não estou certo do que significa 'maior que um flutuador'.

Caso contrário, isso parece razoável, embora eu não esteja 100% certo sobre as letras minúsculas x para o nome da propriedade. O minúsculo parece ir contra as convenções de nomenclatura C # e UWP existentes.

Certamente, a existência dessa propriedade flutuante ajudaria a sinalizar que algo estranho estava acontecendo, o que poderia encorajar as pessoas a verificar os documentos, que então ofereceriam uma explicação.

@ Scottj1s

Após uma discussão relacionada, a proposta foi apresentada para manter os acessadores de propriedade de precisão dupla, digamos Point.X, e adicionar verificação de limites (ou seja, lançar se o duplo for maior do que um flutuante), mas também adicionar acesso direto e eficiente ao único - lojas de apoio de precisão. A estrutura projetada seria blittable, então cruzar o ABI seria barato.

Então,
Ponto pt;
pt.X = GetDouble ();
pt.x = GetFloat ();

pensamentos?

Acho que isso vai causar mais confusão no longo prazo. O que quero dizer é - as pessoas esperam UpperCamelCase, então provavelmente 99,9% usarão .X independentemente.

Meu ponto (trocadilho intencional) é que esta é uma classe cujo uso deve ser totalmente trivial. É um ponto com 2 coordenadas - X, Y - é isso. Por que torná-lo complexo?

Oferecer projeção 'dupla' é um grande erro IMHO:

  1. lidar com (dobrar) infinito não funcionará
  2. Por causa do arredondamento de precisão, acredito que você pode resolver problemas como este
    Point p;
    p.X = some_crazy_double_value;
    if ( p.X == some_crazy_double_value) 
        // will never execute
        do_stuff();

Agradeço o feedback sobre isso.

Temos a opção de manter a compatibilidade de origem (o que não acho que alguém esteja defendendo), com:

struct Point
{
    float _x;
    float _y;

    public Point(double x, double y)
    {
        _x = (float)x;
        _y = (float)y;
    }

    public double X { get => _x; set =>_x = (float)value; }
    public double Y { get => _y; set =>_y = (float)value; }
}

Ou valide campos, talvez com algo assim:

struct Point
{
    public Point(float x, float y)
    {
        _x = x; 
        _y = y;
    }

    public Point(double x, double y)
    {
        _x = (float)x; 
        _y = (float)y;
        Validate(_x, x);
        Validate(_y, y);
    }

    public float _x;
    public double X { get => _x; set { _x = (float)value; Validate(_x, value); } }

    public float _y;
    public double Y { get => _y; set { _y = (float)value; Validate(_y, value); } }

    [Conditional("DEBUG")]  // or "WINRT_VALIDATE"
    void Validate(float field, double value, [CallerMemberName] string member = "")
    {
        if ((double)field != value && !float.IsNaN(field))
        {
            throw new ArgumentOutOfRangeException(member);
        }
    }
};

A segunda versão é efetivamente um superconjunto da estrutura Point existente.

Quaisquer preocupações?

Quaisquer preocupações?

Isso quase sempre joga? Mantissa é mais larga para o dobro, então é quase certo que você perderá informações ao lançar o elenco.

Sim, eu ia dizer que certamente new Point(1.0/3.0, 1.0/3.0) ou qualquer coisa semelhante geraria. O atributo [Conditional ("DEBUG")] significa que a chamada do método não faz nada se construída no modo de liberação?

Você não precisa de nenhum cálculo, ele já falha para constantes simples. Mesmo algo tão simples como 0,7 ou 0,3 seria lançado, já que a mantissa é binária e nenhuma das constantes é representável exatamente. A maioria das frações decimais não são transferidas de forma clara para o binário, pois o decimal tem divisores primos 5 e 2, enquanto o binário tem apenas 2.

Neste ponto, estou argumentando que não há valor em ser compatível com a origem se você estiver jogando com quase qualquer valor de qualquer maneira. Se você quiser ser compatível com o código-fonte, a única opção razoável que posso propor é colocar uma tag Obsolete nela e gerar avisos do compilador, para que as pessoas que se importem possam consertar o código-fonte. A alternativa é quebrar a compatibilidade do código-fonte e forçar todos a corrigirem seus códigos, quer se importem ou não.

Pontos positivos, todos - eu deveria ter testado com alguns decimais simples.

@weltkante , onde você aplicaria o atributo Obsolete? Certamente, to Point (double, double) faz sentido, guiando os usuários para Point (float, float). Mas os próprios acessadores de propriedade X e Y, se obsoletos, não deixariam nenhum caminho para a atribuição de propriedades com segurança por flutuação.

Parece que as opções são deixar como adereços duplos ou mover para adereços flutuantes e fonte de quebra. E esta discussão parece unânime sobre o último. Gostaria de ouvir mais sugestões dos desenvolvedores do winui.

onde você aplicaria o atributo Obsolete?

Sobre os métodos / propriedades que foram considerados como arremessadores, não pensei muito sobre isso. Alguma sugestão acima foi sobre ter campos flutuantes blittable e acessores duplos adicionais. Não necessariamente um fã disso, mas a API dupla pode ser marcada como obsoleta.

Pessoalmente, eu sou a favor de mudar o tipo, me livrar de double onde nativa é apenas float e deixar as pessoas limparem seus erros de construção de acordo com sua preferência (ou fazer o elenco elas mesmas, se não se importarem, ou inspecionar sua fonte e propagar a mudança de tipo onde faz sentido)

Pessoalmente, estou feliz com a mudança do tipo, mas também percebo que pode ser uma mudança significativa.

Acho que é definitivamente necessária uma análise de quantas ramificações existem para cada cenário. Como alguém mencionou anteriormente, acho que as larguras dos elementos e outras coisas podem ter o mesmo problema. Além disso, como foi apontado antes, a definição de valores para Point etc em XAML precisa ser pensada. Além disso, o problema de truncamento ainda existirá em algumas situações em linguagens como JavaScript, que tem apenas um tipo de número, embora isso não seja realmente um motivo para piorar a experiência do C #.

Se você acha que é necessário manter a compatibilidade do código, prefiro algo assim.
`` `C #
struct Point
{
ponto público (flutuante x, flutuante y)
{
_x = x;
_y = y;
}

public Point(double x, double y)
{
    _x = (float)x; 
    _y = (float)y;
}

float _x;
public float XValue { get => _x; set { _x = value;} }
public double X { get => _x; set { _x = (float)value; } }

float _y;
public float YValue { get => _y; set { _y = value;} }
public double Y { get => _y; set { _y = (float)value; } }

}
`` Possibly you could special case some values like infinity, but I'd have to read up a bit more on the technical specifications of float and double to say more. Having written this, it does feel rather horrible to have to use XValue and YValue` ...

Esta é uma diferença problemática. Todo o resto no layout da IU usa duplos, como ActualWidth / Height, Width / Height, GridLength, Value, Thickness, etc. etc. Estaríamos lançando para doubles em todos os lugares.
Um exemplo são as etapas Organizar / Substituir

Um exemplo são as etapas Organizar / Substituir

Você pode compartilhar o código que você tem? Esses são os exemplos perfeitos de que precisamos para tomar as decisões certas.

@stevenbrix aqui é apenas um exemplo simples, mas rapidamente obtém cada vez mais. O código é compilado para UWP, e não para Win32:

        protected override Size ArrangeOverride(Size finalSize)
        {
            int count = Children.Count;
            var width = (finalSize.Width - (count + 1) * (CellMargin.Left - CellMargin.Right)) / finalSize.Width;
            var x = CellMargin.Left;
            foreach(var child in Children)
            {
                child.Arrange(new Rect(x, 0, width, finalSize.Height));
                x += CellMargin.Right + CellMargin.Left;
            }
            return base.ArrangeOverride(finalSize);
        }

        public Thickness CellMargin
        {
            get { return (Thickness)GetValue(CellMarginProperty); }
            set { SetValue(CellMarginProperty, value); }
        }

        public static readonly DependencyProperty CellMarginProperty =
            DependencyProperty.Register("CellMargin", typeof(Thickness), typeof(MyCustomControl), new PropertyMetadata(new Thickness()));

A correção é lançar para flutuar apenas para Win32, o que é feio. Claro, você pode corrigir a inconsistência no UWP para que todos fiquem flutuantes e não tenhamos que #if-def it, mas é estranho que todas as propriedades de layout não correspondam ao tipo de dados.

Além disso, isso é muito perturbador. Você deseja baixo atrito se deseja que as pessoas mudem para uma nova plataforma de IU.

... também para aqueles de nós que reutilizam código com WPF e UWP "antigo", é ainda mais perturbador

Que impacto você espera que essa compatibilidade tenha? Essas propriedades literalmente sempre foram flutuantes, C # apenas nunca as viu como tal porque a projeção estava errada.

  • você espera sempre projetar float para dobrar para C #?
  • ou apenas em APIs WinUI selecionadas?
  • ou no WinUI como uma biblioteca?
  • ou também em bibliotecas C ++ definidas pelo usuário?
  • ou apenas em controles de interface do usuário C ++ definidos pelo usuário WinUI +?

O escopo de preservar o bug de projeção parece muito obscuro para mim. (Talvez eu também esteja perdendo o impacto que o bug de projeção teve originalmente, não reli a discussão e já faz um tempo.)

E quanto às outras propriedades que são "realmente" flutuantes como ActualWidth / Height etc? Tem que ser tudo ou nada, ou IMHO o argumento para essa mudança desmorona. Mas se você fizer tudo, podemos realmente começar a falar sobre uma mudança disruptiva.
Eu realmente não recebo o benefício de mudar para flutuar. Parece mais acadêmico do que sobre algo de que realmente precisamos.

Eles não são alterados, sempre foram flutuantes, a projeção (ou seja, o gerador de código gerando a API C # da API nativa) apenas teve um bug, esse bug foi corrigido. A questão é se você deseja preservar o bug em que escopo deseja preservá-lo.

(Não estou aqui para discutir ou votar em nenhum dos lados, só queria trazer à tona a questão do escopo, porque isso pode não ser necessariamente apenas sobre WinUI, com a projeção sendo reimplementada.)

@weltkante eles mudaram. A superfície da API mudou literalmente. O campo de apoio é irrelevante para os desenvolvedores que o codificam. A questão da precisão também é irrelevante, já que temos que considerar isso também para os duplos.
Além disso, essa mudança não explica por que todos os duplos-que-são-realmente-flutuantes não foram alterados também

UWP 10240 já estava flutuando para membros Point na API. Apenas C # viu isso como o dobro, pelo que eu entendi. Talvez eu esteja entendendo mal as coisas, não estou excluindo isso.

Portanto, não é uma mudança na API WinUI, é uma mudança na projeção, e se a dupla ilusão deve ser perpetuada aí, a questão é em qual escopo.

Portanto, não é uma mudança na API WinUI, é uma mudança na projeção,

Isso mesmo, isso é exatamente o que é. A API WinRT era e é de precisão única, assim como os campos na projeção .Net Native. Mas essa projeção tem propriedades de dois tipos que envolvem os campos e fazem a conversão do tipo.

Portanto, este problema é especificamente sobre a projeção. E a projeção de preview para .Net5 não tem a conversão dupla.

@dotMorten Eu concordo que provavelmente faria sentido mudar todos os duplos-que-são-realmente-flutuantes para flutuantes, se isso acontecer. Isso está sendo considerado? Em um mundo ideal, acho que a melhor solução seria que todos os duplos-que-são-realmente-flutuantes seriam na verdade duplos-que-são-realmente-duplos, mas isso não vai acontecer até onde posso ver. Não sei mais qual é a melhor solução. Acho que deixar bem claro na documentação e no intellisense que as propriedades são, na verdade, apenas de precisão simples, embora sejam duplas, seja o melhor caminho a seguir, junto com talvez um acesso direto aos flutuadores conforme descrito em https: // github .com / microsoft / microsoft-ui-xaml / issues / 2047 # issuecomment -605512398. É importante saber pelo menos que essas propriedades são realmente de precisão única ao fazer coisas como cálculos de geometria coordenada que requerem uma tolerância de erro.

Eu não sei mais qual é a melhor solução

Mesmo mesmo :)

Não acho que mover todos os tipos de WinUI para float seja viável. Seria uma alteração muito grande que tornaria a migração mais difícil.

Acho que é mais importante manter a compatibilidade do código-fonte, e a dor que isso causa às pessoas que desejam interoperar com WPF / UWP não vale a pena mudar.

Também gosto da sugestão desse comentário, embora não seja realmente um fã das propriedades XValue e YValue . Poderíamos fornecer os construtores como Scott sugeriu e fazer com que um analisador Roslyn detectasse o uso do construtor que pega double e fornece uma pequena lâmpada que diz que isso pode truncar. Ou ainda mais simples, basta ter comentários xml que forneceriam texto de ajuda do Intellisense

struct Point
{
    public Point(float x, float y)
    {
        _x = x; 
        _y = y;
    }

   /// <summary>
   /// Warning: The values for X and Y could be truncated if you require greater precision due to the backing storage being float.
   /// </summary>
    public Point(double x, double y)
    {
        _x = (float)x; 
        _y = (float)y;
    }

    float _x;
    public double X { get => _x; set { _x = (float)value; } }

    float _y;
    public double Y { get => _y; set { _y = (float)value; } }

}

Estou meio indeciso agora que percebi que o tipo do WinRT é float. É feio de se lidar, mas também entendo agora.

Se você mantiver os padrões padrão para Win32 e UWP, há uma maneira de permitir que os desenvolvedores substituam o double e usem um float?

Esta é uma decisão difícil, e é por isso que estamos visualizando a mudança de double para float, para que possamos coletar feedback da comunidade. Em relação à conversão abrangente de duplo para flutuante, é um problema apenas para as 3 estruturas geométricas. Essas eram conversões de mapeamento de tipo WinRT / .NET personalizadas . Observe que nenhum outro mapeamento de tipo de valor personalizado no link acima possui campos baseados em float - eles são todos duplos ou integrais. Isso inclui todos os tipos Windows.UI.Xaml * (por exemplo, Thickness) e tipos de sistema como TimeSpan. Portanto, a diferença de impedância que precisamos resolver é apenas para esses 3 tipos.

@mdtauk , sim, certamente poderíamos fornecer compilação condicional para alternar entre double e float nos acessadores de propriedade, construtores

Eu espalhei os cabeçalhos do meu código com o seguinte:

#if WINUI && NETCOREAPP
using uiunits = System.Single; 
#else
using uiunits = System.Double;
#endif

Em seguida, use var vez de double para confiar em tipos implícitos e, geralmente, lançar assim:
new Point((uiunits)x, (uiunits)y);

Isso tornou as alterações de código um pouco mais suportáveis, mas ainda faltavam 100 arquivos para mexer.

(observe que a maior parte deste código compila para WPF, UWP e WinUI)

Aqui está uma ideia alternativa: Apresente Microsoft.UI.Point / Rect / Size e use-os para todo o layout da IU, e faça-os de dupla precisão, para que não tenhamos para fazer todo esse lançamento e para trás, e pare de usar Windows.Foundation.Point / Rect / Size . De qualquer maneira, parecia estranho que esses tipos de janelas fossem usados ​​em todo o layout.

Observe que nenhum outro mapeamento de tipo de valor personalizado no link acima possui campos baseados em float - eles são todos duplos ou integrais.

@ Scottj1s - isso pode ser verdade em termos de tipos, mas é verdade para todas as propriedades que são projetadas como duplas, por exemplo, ActualHeight, ActualWidth? Eu não testei, mas como a nova propriedade ActualSize é de ponto flutuante, acho difícil acreditar que ActualWidth e ActualHeight sejam realmente duplos. Suponho que isso não importe muito, já que você não pode defini-los, mas ainda seria bom ser consistente.

@ benstevens48 - Eu estava apenas me referindo à dor de mudança para consertar o que antes era projetado (incorretamente) como duplos - os tipos de geometria. ActualHeight e ActualWidth podem muito bem ser armazenados internamente como flutuantes, dado que ActualSize é um vetor2, como você indicou. Mas isso é um detalhe de implementação de UIElement / FrameworkElement. Essas propriedades já são projetadas fielmente como duplas.
@dotMorten - Acho que pode ser uma alteração ainda maior. Gostaria de ouvir a equipe WinUI opinar sobre essa sugestão.

🦙 Cross-linking para o novo repositório CS / WinRT .

@dotMorten , et al - obrigado pelo feedback. Revisamos a mudança das propriedades double para float em Rect, Point e Size. Embora torne a projeção tecnicamente "correta", ela cria impedância demais com outras propriedades e variáveis ​​duplas e cria grandes problemas de compatibilidade. Portanto, decidimos reverter a mudança.

@ Scottj1s - você planeja expor o acesso direto às propriedades float (por meio de nomes de variáveis ​​alternativos), e talvez um construtor float, conforme discutido anteriormente neste tópico?

@ benstevens48 - obrigado pelo lembrete. Sim, adicionarei construtores de float e também tornarei públicos os campos de apoio para essas estruturas. Claramente, este é um caso especial que justifica quebrar as regras usuais de ocultação de dados, etc.

@ Scottj1s - você pode considerar a exposição dos campos de apoio por meio de um getter / setter de propriedade que está em conformidade com as convenções de nomenclatura C #?

@ benstevens48 o que exatamente você está tentando realizar? É para ser usado com o WinUI ou você está tentando usar esses tipos fora do WinUI?

@ benstevens48 - você pode fornecer um exemplo específico, digamos para Point? Isso é para fins vinculativos?

Na verdade, talvez seja melhor apenas expor as propriedades duplas.

Minha principal preocupação é que deve ser claramente documentado que a precisão real armazenada é única. Isso deve incluir os documentos que você obtém no itellisense e passa o mouse sobre o campo, porque às vezes é tudo que uso. Quero evitar que outros cometam o mesmo erro que eu cometi ao usar essas estruturas para coordenadas de GPS e os outros problemas que mencionei no início.

Meu principal caso de uso original fora do WinUI foi para um ponto / vetor2 de precisão dupla (já que ainda .net não o tem), mas obviamente isso não é possível, embora seja muito tentador usar o Point para isso, por isso meu apelo para documentos claros.

O outro uso principal que tenho é a classe Rect com Win2D. Na verdade, eu escrevo código em C # e C ++ para isso, às vezes copiando entre eles, e é um pouco estranho ter flutuadores para um e duplos para o outro, mas expor os flutuantes não ajudaria muito a menos que os nomes fossem os mesmos, mas Não tenho certeza se expor nomes que não estão em conformidade com as convenções C # seja uma boa ideia.

Como as telas estão ficando maiores, faz sentido, em vez disso, tornar os campos de apoio double precisão uma vez que a superfície da API já está dizendo que eles são assim? Ou haveria tantas ramificações de mudar isso?

Alterar os campos de apoio é uma mudança na ABI, com ondulações muito maiores e problemas de compatibilidade, afetando outras linguagens e implementações de componentes que usam esses tipos. Alterar tudo o mais (construtores, acessadores de propriedade, métodos, etc.) afeta apenas o código do cliente C #.

Meu principal caso de uso original fora do WinUI foi para um ponto / vetor de precisão dupla 2

Existem métodos de extensão para isso:

image

@jtorjo - Quero dizer, eu queria uma maneira de armazenar um par de valores de precisão dupla. Obviamente, usar Point acabou sendo errado, mas o homem demorou um pouco para perceber. .Net tem apenas um Vector2 de precisão atualmente (embora haja uma proposta dupla, eu acho).

Este é um daqueles erros de design que se revelam muito dolorosos mais tarde - pelo atrito que causa aos desenvolvedores de qualquer maneira. Deixar como está e escolher consertá-lo apresenta desafios. Em algum ponto, uma alteração de metadados pode ser considerada para WinUI (como foi sugerido). Novas interfaces / métodos podem então ser aceitos. Enquanto isso, achamos melhor preservar o comportamento legado na camada de projeção (agora fornecida por C # / WinRT) para evitar surpresas e inconsistências.

Esta página foi útil?
0 / 5 - 0 avaliações