c# casting oddity — any explanations?

huangapple go评论121阅读模式
英文:

c# casting oddity -- any explanations?

问题

这段代码中的问题在于数据类型转换。第一段代码中,您使用了中间变量 odf,首先将 row[i + 2] 转换为 double 类型,然后再将其转换为 float 类型。这种逐步的转换可能导致了数据类型匹配,因此没有出现问题。

而在第二段代码中,您直接将 row[i+2] 转换为 float,这可能会导致数据类型不匹配,从而出现 "Specified cast is not valid" 错误。

要解决这个问题,您可以尝试在第二段代码中使用与第一段相同的中间变量来进行逐步转换,或者确保 row[i+2] 的数据类型与 float 兼容。

英文:

Using System.Data.SqlClient & ADO.

How come this code with casting works fine:

foreach (DataRow row in dsUtilities.Tables[0].Rows)
{
     o = row[i + 2];
     d = (double)o;
     f = (float)d;
     u.CostMonth[i] = f; ...

But this code with casting crashes:

foreach (DataRow row in dsUtilities.Tables[0].Rows)
{
     u.CostMonth[i] = (float)row[i+2]; 

"Specified cast is not valid"

For context, below is the complete routine....
I can do it the long way as a workaround, but I want to know if I'm doing something wrong, or it this a plainly a oddity of the language?

    private void LoadUtilities()
    {
        /* Normally this should only be called once, and then keep the dataset current, and update db separately... */
        string CS = ConfigurationManager.ConnectionStrings["DbConn"].ToString();
        dsUtilities = null;
        lstUtilities = null;

        lstUtilities = new List<clsUtility>();

        this.dtpLastUtilityRefreshDate.Value = DateTimePicker.MinimumDateTime;
        lblAsterisk.Visible = false;
        txtWarning.Visible = false;


        using (SqlConnection con = new SqlConnection(CS))
        {
            SqlDataAdapter da = new SqlDataAdapter("GetUtilities", con); // Using a Store Procedure.
                                                                           //SqlDataAdapter da = new SqlDataAdapter("SELECT 'this is a test text' as test", con); To use a hard coded query.
            da.SelectCommand.CommandType = CommandType.StoredProcedure; 
            dsUtilities = new DataSet(); 
            //da.SelectCommand.Parameters.AddWithValue("@ggg", 95); // Repeat for each parameter present in the Store Procedure.

            da.Fill(dsUtilities); // Fill the dataset with the query data


            foreach (DataRow row in dsUtilities.Tables[0].Rows)
            {
                clsUtility u;
                u = new clsUtility();
                u.UtilityId = (int)row[0];
                u.Utility = (string)row[1];

                if (row[2] is System.DBNull)
                {
                    u.UNC_SourcePath = @"C:\";
                }
                else u.UNC_SourcePath = (string)row[2];

                int i;
                i = 1;
                object o;
                double d;
                float f;
                while (i < 13)
                {                       
                    if (row[i+2] is System.DBNull) { } else 
                    {
                        o = row[i + 2];
                        d = (double)o;
                        f = (float)d;
                        u.CostMonth[i] = f;
                        u.CostMonth[i] = (float)row[i+2]; 
                    }

                    i++;
                }

                if (row[16] is System.DBNull) { } else { u.LineLossPct = (float)row[16]; }

                if (row[17] is System.DBNull) { } else { u.GRT = (float)row[17]; }
                if (row[18] is System.DBNull) { } else { u.POR = (float)row[18]; }
                if (row[19] is System.DBNull) { } else { u._12MonthCaps = (float)row[19]; }
                if (row[20] is System.DBNull) { } else { u._12MonthNits = (float)row[20]; }
                if (row[21] is System.DBNull) { } else { u._12MonthRate = (float)row[21]; }
                if (row[22] is System.DBNull) { } else { u.NonPolarCTA = (float)row[22]; }
                if (row[23] is System.DBNull) { } else { u.PolarCTA = (float)row[23]; }

                if (row[24] is System.DBNull)
                {
                    u.LastUploadedByTitan = DateTimePicker.MinimumDateTime;
                }
                else
                {
                    u.LastUploadedByTitan = (DateTime)row[24];
                }


                if (row[25] is System.DBNull)
                {
                    u.LastRefreshedByUtility = DateTimePicker.MinimumDateTime;
                }
                else
                {
                    u.LastRefreshedByUtility = (DateTime)row[25];
                }

                this.lstUtilities.Add(u);
                u = null;
            }


            this.cboUtility.DataSource = lstUtilities;
            this.cboUtility.DisplayMember = "Utility";
            this.cboUtility.ValueMember= "UtilityId";

            this.cboUtility.SelectedIndex = 0;

            GetDateStamps();

        }


    }






da.Fill(dsUtilities); // Fill the dataset with the query data


foreach (DataRow row in dsUtilities.Tables[0].Rows)
{
    clsUtility u;
    u = new clsUtility();
    u.UtilityId = (int)row[0];
    u.Utility = (string)row[1];

    if (row[2] is System.DBNull)
    {
        u.UNC_SourcePath = @"C:\";
    }
    else u.UNC_SourcePath = (string)row[2];

    int i;
    i = 1;
    object o;
    double d;
    float f;
    while (i < 13)
    {                       
        if (row[i+2] is System.DBNull) { } else 
        {
            o = row[i + 2];
            d = (double)o;
            f = (float)d;
            u.CostMonth[i] = f;
            u.CostMonth[i] = (float)row[i+2]; 
        }

        i++;
    } ….

答案1

得分: 4

以下是翻译好的部分:

混淆源于语法 (T)x(其中 T 是类型,x 是变量),实际上可以根据上下文意义不同。

内置数值转换

C# 语言定义了许多数值类型之间的转换。其中一些是需要使用强制转换语法的显式转换,例如:

double d = 3.0;
float f = (float)d;

内置数值转换会根据涉及的类型编译成相应的 IL 指令,例如 conv 系列。

拆箱

当将值类型分配给 object 类型(或其他引用类型)的变量时,会发生装箱,导致值类型存储在堆上的盒子中。拆箱用于将值类型从盒子中取出。这里也使用了强制转换语法,但强制转换中使用的类型必须与盒子中的对象类型匹配。

object box = 3.0;
double d = (double)box; // OK:盒子中包含 double
float f = (float)box; // Error:盒子中包含 double,而不是 float

拆箱编译成 unbox IL 指令。

用户定义的类型转换

在 C# 中,类型可以定义与其他类型之间的转换。这些转换可以是显式的,如果是这种情况,就需要使用强制转换语法来调用它们。

例如,Decimal 类型有以下定义(https://referencesource.microsoft.com/#mscorlib/system/decimal.cs,1110):

public static explicit operator float(Decimal value) {
    return ToSingle(value);
}

可以通过以下方式调用上面定义的运算符:

decimal m = 3.0m;
float f = (float)m;

上面定义的运算符被编译成名为 op_Explicit(decimal m) 的静态方法,而强制转换语法被编译成对此方法的调用。

其他

还有其他类型的显式转换(引用类型、动态类型、可空类型),这里不相关,但也使用强制转换语法。


搞清楚了这些,你的代码实际上执行了拆箱,然后进行内置数值转换:

o = row[i + 2];
d = (double)o; // 拆箱
f = (float)d; // 内置数值转换

你不能写成 float f = (float)o,因为那会被解释为尝试将 o 视为 float 进行拆箱,实际上它包含的是装箱的 double

你需要分为两个步骤,第一个步骤是拆箱,第二个步骤是转换为 float

当然,你也可以在一行中写成:

float f = (float)(double)row[i + 2];
英文:

The confusion stems from the fact that the syntax (T)x (where T is a type and x is a variable) can actually mean different things, depending on context.

Built-in numeric conversions

The C# language defines a number of conversions between numeric types. Some of these are explicit conversions which require the cast syntax, e.g.:

double d = 3.0;
float f = (float)d;

Built-in numeric conversions compile to appropriate IL instructions depending on the types convolved, e.g. the conv family.

Unboxing

Boxing occurs when a value type is assigned to a variable of type object (or another reference type), and results in the value type being stored in a box on the heap. Unboxing is used to get the value type back out of the box. The cast syntax is used here as well, but the type used in the cast must match the type of the boxed object.

object box = 3.0;
double d = (double)box; // OK: the box contains a double
float f = (float)box; // Error: the box contains a double, not a float

Unboxing compiles to the unbox IL instruction.

User-defined type conversions

Types in C# can define conversions to or from other types. Such conversions can be explicit, in which case the cast syntax is required to invoke them.

For example, the Decimal type has the following defined:

public static explicit operator float(Decimal value) {
    return ToSingle(value);
}

This would be invoked by e.g.:

decimal m = 3.0m;
float f = (float)m;

The operator defined above is compiled to a static method called op_Explicit(decimal m), and the cast syntax compiles to a call to this method.

Others

There are other types of explicit conversions (reference, dynamic, nullable) which aren't relevant here, but also use the cast syntax.


With this out of the way, your code is actually doing an unbox followed by a built-in numeric conversion:

o = row[i + 2];
d = (double)o; // Unbox
f = (float)d; // Built-in numeric conversion

You can't write float f = (float)o, because that's interpreted as trying to unbox o as a float, when actually it contains a boxed double.

You need to have two steps, the first to unbox, and the second to convert to a float.

You can of course write it on a single line, as:

float f = (float)(double)row[i + 2];

huangapple
  • 本文由 发表于 2020年1月3日 22:54:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/59580693.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定